feat: new MCP tools #10
+125
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user