feat: new MCP tools #10

Merged
jmiller merged 1 commits from dev into main 2026-05-23 00:58:27 +00:00
+125
View File
@@ -750,6 +750,131 @@ server.tool(
async ({ owner, repo, run_id, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/actions/runs/${run_id}`)),
);
server.tool(
'gitea_actions_dispatch',
'Trigger a workflow dispatch (e.g. pre-release, deploy)',
{
...OwnerRepo,
workflow: z.string().describe('Workflow filename (e.g. pre-release.yml)'),
ref: z.string().describe('Branch or tag to run on (e.g. dev, main)'),
inputs: z.record(z.string()).optional().describe('Workflow input key-value pairs'),
...ConnectionParam,
},
async ({ owner, repo, workflow, ref, inputs, connection }) =>
formatResponse(await clientFor(connection).post(
`/repos/${owner}/${repo}/actions/workflows/${workflow}/dispatches`,
{ ref, inputs: inputs ?? {} },
)),
);
server.tool(
'gitea_actions_jobs_list',
'List jobs for a workflow run',
{ ...OwnerRepo, run_id: z.number().describe('Run ID'), ...ConnectionParam },
async ({ owner, repo, run_id, connection }) =>
formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/actions/runs/${run_id}/jobs`)),
);
server.tool(
'gitea_actions_job_logs',
'Get log output for a workflow job',
{ ...OwnerRepo, job_id: z.number().describe('Job ID'), ...ConnectionParam },
async ({ owner, repo, job_id, connection }) => {
const client = clientFor(connection);
const res = await client.get(`/repos/${owner}/${repo}/actions/jobs/${job_id}/logs`);
if (res.status >= 400) return formatResponse(res);
// Logs come as plain text
const text = typeof res.data === 'string' ? res.data : JSON.stringify(res.data);
return { content: [{ type: 'text' as const, text }] };
},
);
server.tool(
'gitea_release_asset_upload',
'Upload a file as a release asset (provide base64-encoded content)',
{
...OwnerRepo,
release_id: z.number().describe('Release ID'),
name: z.string().describe('Asset filename'),
content_base64: z.string().describe('Base64-encoded file content'),
...ConnectionParam,
},
async ({ owner, repo, release_id, name, content_base64, connection }) => {
const client = clientFor(connection);
// Gitea expects multipart form data for asset upload
// For now, use the API with the binary content
const res = await client.post(
`/repos/${owner}/${repo}/releases/${release_id}/assets?name=${encodeURIComponent(name)}`,
Buffer.from(content_base64, 'base64'),
);
return formatResponse(res);
},
);
server.tool(
'gitea_release_asset_delete',
'Delete a release asset',
{
...OwnerRepo,
release_id: z.number().describe('Release ID'),
asset_id: z.number().describe('Asset ID'),
...ConnectionParam,
},
async ({ owner, repo, release_id, asset_id, connection }) =>
formatResponse(await clientFor(connection).delete(`/repos/${owner}/${repo}/releases/${release_id}/assets/${asset_id}`)),
);
server.tool(
'gitea_bulk_file_push',
'Push the same file content to multiple repos (uses Contents API)',
{
owner: z.string().describe('Organization name'),
repos: z.array(z.string()).describe('List of repository names'),
path: z.string().describe('File path in each repo (e.g. .mokogitea/workflows/pre-release.yml)'),
content_base64: z.string().describe('Base64-encoded file content'),
message: z.string().describe('Commit message'),
branch: z.string().optional().describe('Target branch (default: main)'),
...ConnectionParam,
},
async ({ owner, repos, path, content_base64, message, branch, connection }) => {
const client = clientFor(connection);
const targetBranch = branch ?? 'main';
const results: Array<{ repo: string; status: string }> = [];
for (const repo of repos) {
try {
// Get current file SHA
const existing = await client.get(`/repos/${owner}/${repo}/contents/${path}?ref=${targetBranch}`);
const sha = (existing.data as { sha?: string })?.sha;
if (sha) {
// Update existing file
await client.put(`/repos/${owner}/${repo}/contents/${path}`, {
content: content_base64,
sha,
message,
branch: targetBranch,
});
results.push({ repo, status: 'updated' });
} else {
// Create new file
await client.post(`/repos/${owner}/${repo}/contents/${path}`, {
content: content_base64,
message,
branch: targetBranch,
});
results.push({ repo, status: 'created' });
}
} catch (e) {
results.push({ repo, status: `error: ${e}` });
}
}
const summary = results.map(r => `${r.repo}: ${r.status}`).join('\n');
return { content: [{ type: 'text' as const, text: summary }] };
},
);
// ── Organizations ───────────────────────────────────────────────────────
server.tool(