@finchagentic/mcp 4.6.0 → 4.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/package.json +5 -6
- package/dist/_http-cache.js +0 -96
- package/dist/agent-loop.js +0 -301
- package/dist/annotations.js +0 -122
- package/dist/cli.js +0 -1391
- package/dist/clink-input.js +0 -15
- package/dist/config.js +0 -132
- package/dist/convex.js +0 -175
- package/dist/dex-pair.js +0 -54
- package/dist/enrichment-router.js +0 -315
- package/dist/index.js +0 -258
- package/dist/llm.js +0 -298
- package/dist/local-memory-file.js +0 -147
- package/dist/local-memory.js +0 -135
- package/dist/local-vault.js +0 -454
- package/dist/output-schemas.js +0 -605
- package/dist/project.js +0 -36
- package/dist/prompts.js +0 -111
- package/dist/public-url.js +0 -107
- package/dist/resources.js +0 -111
- package/dist/server.js +0 -322
- package/dist/signal-gate.js +0 -57
- package/dist/token-decimals.js +0 -26
- package/dist/token-gate.js +0 -88
- package/dist/tool-filter.js +0 -53
- package/dist/tools/_solidity-scan.js +0 -313
- package/dist/tools/agents.js +0 -441
- package/dist/tools/automation.js +0 -354
- package/dist/tools/base-mcp.js +0 -466
- package/dist/tools/base.js +0 -283
- package/dist/tools/chronicle.js +0 -268
- package/dist/tools/coder.js +0 -94
- package/dist/tools/deep-research.js +0 -1421
- package/dist/tools/defi.js +0 -292
- package/dist/tools/equity.js +0 -372
- package/dist/tools/events.js +0 -182
- package/dist/tools/github.js +0 -564
- package/dist/tools/insider.js +0 -264
- package/dist/tools/insight.js +0 -630
- package/dist/tools/market.js +0 -555
- package/dist/tools/memory.js +0 -1044
- package/dist/tools/miroshark.js +0 -350
- package/dist/tools/monitor.js +0 -319
- package/dist/tools/os.js +0 -236
- package/dist/tools/packets.js +0 -296
- package/dist/tools/research-chain.js +0 -226
- package/dist/tools/research-compare.js +0 -280
- package/dist/tools/research.js +0 -188
- package/dist/tools/rh-bridge.js +0 -148
- package/dist/tools/rh-mcp.js +0 -1448
- package/dist/tools/rh-orders.js +0 -556
- package/dist/tools/scanner.js +0 -564
- package/dist/tools/stake.js +0 -369
- package/dist/tools/vault.js +0 -1020
- package/dist/tools/wallet.js +0 -200
- package/dist/types.js +0 -2
- package/dist/wallet.js +0 -372
package/dist/tools/github.js
DELETED
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.GITHUB_TOOLS = void 0;
|
|
37
|
-
exports.buildRepoList = buildRepoList;
|
|
38
|
-
exports.buildPrList = buildPrList;
|
|
39
|
-
exports.buildPrDetail = buildPrDetail;
|
|
40
|
-
exports.buildIssueList = buildIssueList;
|
|
41
|
-
exports.buildIssueDetail = buildIssueDetail;
|
|
42
|
-
exports.buildCommitList = buildCommitList;
|
|
43
|
-
exports.buildCodeSearch = buildCodeSearch;
|
|
44
|
-
exports.handleGithubTool = handleGithubTool;
|
|
45
|
-
const fs = __importStar(require("fs"));
|
|
46
|
-
const path = __importStar(require("path"));
|
|
47
|
-
const GH_BASE = "https://api.github.com";
|
|
48
|
-
// Was hardcoded "finch-mcp/3.28.0" - stale the moment package.json's version
|
|
49
|
-
// moved on (already 4.4.0 locally as of this fix), unlike server.ts/cli.ts/
|
|
50
|
-
// index.ts which all read it from package.json at runtime per this repo's
|
|
51
|
-
// own stated policy (see CLAUDE.md's mcp-server note). __dirname here is
|
|
52
|
-
// dist/tools/ once built, so two levels up reaches the package root - one
|
|
53
|
-
// level deeper than server.ts's own copy of this pattern (dist/).
|
|
54
|
-
const PKG_VERSION = (() => {
|
|
55
|
-
try {
|
|
56
|
-
const raw = fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8");
|
|
57
|
-
return JSON.parse(raw).version ?? "unknown";
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
return "unknown";
|
|
61
|
-
}
|
|
62
|
-
})();
|
|
63
|
-
function ghHeaders() {
|
|
64
|
-
const token = process.env.GITHUB_TOKEN;
|
|
65
|
-
const h = {
|
|
66
|
-
Accept: "application/vnd.github.v3+json",
|
|
67
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
68
|
-
"User-Agent": `finch-mcp/${PKG_VERSION}`,
|
|
69
|
-
};
|
|
70
|
-
if (token)
|
|
71
|
-
h.Authorization = `Bearer ${token}`;
|
|
72
|
-
return h;
|
|
73
|
-
}
|
|
74
|
-
async function gh(path) {
|
|
75
|
-
const res = await fetch(`${GH_BASE}${path}`, {
|
|
76
|
-
headers: ghHeaders(),
|
|
77
|
-
signal: AbortSignal.timeout(12000),
|
|
78
|
-
});
|
|
79
|
-
if (!res.ok) {
|
|
80
|
-
const err = await res.json().catch(() => ({}));
|
|
81
|
-
throw new Error(`GitHub ${res.status}: ${err.message ?? res.statusText}`);
|
|
82
|
-
}
|
|
83
|
-
return res.json();
|
|
84
|
-
}
|
|
85
|
-
function md(text, structuredContent) {
|
|
86
|
-
return structuredContent
|
|
87
|
-
? { content: [{ type: "text", text }], structuredContent }
|
|
88
|
-
: { content: [{ type: "text", text }] };
|
|
89
|
-
}
|
|
90
|
-
// ── Structured output builders (MCP outputSchema payloads in output-schemas.ts) ──
|
|
91
|
-
function buildRepoList(username, repos) {
|
|
92
|
-
return {
|
|
93
|
-
username: username ?? null,
|
|
94
|
-
count: repos.length,
|
|
95
|
-
repos: repos.map((r) => ({
|
|
96
|
-
fullName: r.full_name,
|
|
97
|
-
description: r.description ?? null,
|
|
98
|
-
language: r.language ?? null,
|
|
99
|
-
stars: r.stargazers_count ?? 0,
|
|
100
|
-
forks: r.forks_count ?? 0,
|
|
101
|
-
private: !!r.private,
|
|
102
|
-
updatedAt: r.updated_at ?? null,
|
|
103
|
-
url: r.html_url,
|
|
104
|
-
})),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function buildPrList(owner, repo, state, prs) {
|
|
108
|
-
return {
|
|
109
|
-
owner, repo, state,
|
|
110
|
-
count: prs.length,
|
|
111
|
-
prs: prs.map((p) => ({
|
|
112
|
-
number: p.number,
|
|
113
|
-
title: p.title,
|
|
114
|
-
author: p.user?.login ?? null,
|
|
115
|
-
draft: !!p.draft,
|
|
116
|
-
head: p.head?.ref ?? null,
|
|
117
|
-
base: p.base?.ref ?? null,
|
|
118
|
-
additions: p.additions ?? null,
|
|
119
|
-
deletions: p.deletions ?? null,
|
|
120
|
-
url: p.html_url,
|
|
121
|
-
updatedAt: p.updated_at ?? null,
|
|
122
|
-
})),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
function buildPrDetail(pr, files, reviews, comments) {
|
|
126
|
-
return {
|
|
127
|
-
number: pr.number,
|
|
128
|
-
title: pr.title,
|
|
129
|
-
state: pr.state,
|
|
130
|
-
draft: !!pr.draft,
|
|
131
|
-
author: pr.user?.login ?? null,
|
|
132
|
-
head: pr.head?.ref ?? null,
|
|
133
|
-
base: pr.base?.ref ?? null,
|
|
134
|
-
additions: pr.additions ?? 0,
|
|
135
|
-
deletions: pr.deletions ?? 0,
|
|
136
|
-
changedFiles: pr.changed_files ?? 0,
|
|
137
|
-
url: pr.html_url,
|
|
138
|
-
files: (files ?? []).slice(0, 50).map((f) => ({
|
|
139
|
-
filename: f.filename, status: f.status, additions: f.additions, deletions: f.deletions,
|
|
140
|
-
})),
|
|
141
|
-
reviewCount: (reviews ?? []).length,
|
|
142
|
-
commentCount: (comments ?? []).length,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
function buildIssueList(owner, repo, state, issues) {
|
|
146
|
-
return {
|
|
147
|
-
owner, repo, state,
|
|
148
|
-
count: issues.length,
|
|
149
|
-
issues: issues.map((i) => ({
|
|
150
|
-
number: i.number,
|
|
151
|
-
title: i.title,
|
|
152
|
-
author: i.user?.login ?? null,
|
|
153
|
-
labels: (i.labels ?? []).map((l) => l.name),
|
|
154
|
-
comments: i.comments ?? 0,
|
|
155
|
-
url: i.html_url,
|
|
156
|
-
updatedAt: i.updated_at ?? null,
|
|
157
|
-
})),
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
function buildIssueDetail(issue, comments) {
|
|
161
|
-
return {
|
|
162
|
-
number: issue.number,
|
|
163
|
-
title: issue.title,
|
|
164
|
-
state: issue.state,
|
|
165
|
-
author: issue.user?.login ?? null,
|
|
166
|
-
labels: (issue.labels ?? []).map((l) => l.name),
|
|
167
|
-
assignees: (issue.assignees ?? []).map((a) => a.login),
|
|
168
|
-
commentCount: (comments ?? []).length,
|
|
169
|
-
url: issue.html_url,
|
|
170
|
-
createdAt: issue.created_at ?? null,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
function buildCommitList(owner, repo, branch, path, commits) {
|
|
174
|
-
return {
|
|
175
|
-
owner, repo,
|
|
176
|
-
branch: branch ?? null,
|
|
177
|
-
path: path ?? null,
|
|
178
|
-
count: commits.length,
|
|
179
|
-
commits: commits.map((c) => ({
|
|
180
|
-
sha: c.sha,
|
|
181
|
-
message: c.commit?.message?.split("\n")[0] ?? null,
|
|
182
|
-
author: c.commit?.author?.name ?? c.author?.login ?? null,
|
|
183
|
-
date: c.commit?.author?.date ?? null,
|
|
184
|
-
url: c.html_url,
|
|
185
|
-
})),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
function buildCodeSearch(query, totalCount, items) {
|
|
189
|
-
return {
|
|
190
|
-
query,
|
|
191
|
-
totalCount: totalCount ?? items.length,
|
|
192
|
-
count: items.length,
|
|
193
|
-
results: items.map((it) => ({ path: it.path, repo: it.repository?.full_name ?? null, url: it.html_url })),
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
function fmtDate(iso) {
|
|
197
|
-
if (!iso)
|
|
198
|
-
return "—";
|
|
199
|
-
return new Date(iso).toISOString().slice(0, 10);
|
|
200
|
-
}
|
|
201
|
-
function langExt(filename) {
|
|
202
|
-
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
203
|
-
const map = {
|
|
204
|
-
ts: "typescript", tsx: "typescript", js: "javascript", jsx: "javascript",
|
|
205
|
-
py: "python", rb: "ruby", go: "go", rs: "rust", java: "java",
|
|
206
|
-
cs: "csharp", cpp: "cpp", c: "c", sh: "bash", yaml: "yaml", yml: "yaml",
|
|
207
|
-
json: "json", toml: "toml", md: "markdown", html: "html", css: "css", sql: "sql",
|
|
208
|
-
};
|
|
209
|
-
return map[ext] ?? ext;
|
|
210
|
-
}
|
|
211
|
-
exports.GITHUB_TOOLS = [
|
|
212
|
-
{
|
|
213
|
-
name: "github_list_repos",
|
|
214
|
-
description: "List GitHub repositories for a user or org. Leave username empty to list your own repos (requires GITHUB_TOKEN). " +
|
|
215
|
-
"Returns name, description, language, stars, forks, last updated.",
|
|
216
|
-
inputSchema: {
|
|
217
|
-
type: "object",
|
|
218
|
-
properties: {
|
|
219
|
-
username: { type: "string", description: "GitHub username or org. Empty = your own repos (needs GITHUB_TOKEN)." },
|
|
220
|
-
sort: { type: "string", enum: ["updated", "created", "pushed", "full_name"], description: "Sort field (default: updated)" },
|
|
221
|
-
per_page: { type: "number", description: "Max results (default 20, max 100)" },
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
name: "github_list_prs",
|
|
227
|
-
description: "List pull requests for a GitHub repository. Returns PR number, title, author, state, branch, additions/deletions.",
|
|
228
|
-
inputSchema: {
|
|
229
|
-
type: "object",
|
|
230
|
-
properties: {
|
|
231
|
-
owner: { type: "string", description: "Repo owner (user or org)" },
|
|
232
|
-
repo: { type: "string", description: "Repository name" },
|
|
233
|
-
state: { type: "string", enum: ["open", "closed", "all"], description: "PR state (default: open)" },
|
|
234
|
-
per_page: { type: "number", description: "Max results (default 15, max 100)" },
|
|
235
|
-
},
|
|
236
|
-
required: ["owner", "repo"],
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: "github_get_pr",
|
|
241
|
-
description: "Get full details of a pull request - title, body, diff summary, changed files, reviews, and comments. " +
|
|
242
|
-
"Use to understand what a PR does before reviewing or merging.",
|
|
243
|
-
inputSchema: {
|
|
244
|
-
type: "object",
|
|
245
|
-
properties: {
|
|
246
|
-
owner: { type: "string", description: "Repo owner" },
|
|
247
|
-
repo: { type: "string", description: "Repository name" },
|
|
248
|
-
pr_number: { type: "number", description: "Pull request number" },
|
|
249
|
-
},
|
|
250
|
-
required: ["owner", "repo", "pr_number"],
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
name: "github_list_issues",
|
|
255
|
-
description: "List issues for a GitHub repository. Returns issue number, title, author, labels, comment count.",
|
|
256
|
-
inputSchema: {
|
|
257
|
-
type: "object",
|
|
258
|
-
properties: {
|
|
259
|
-
owner: { type: "string", description: "Repo owner" },
|
|
260
|
-
repo: { type: "string", description: "Repository name" },
|
|
261
|
-
state: { type: "string", enum: ["open", "closed", "all"], description: "Issue state (default: open)" },
|
|
262
|
-
labels: { type: "string", description: "Comma-separated label names to filter by" },
|
|
263
|
-
per_page: { type: "number", description: "Max results (default 15, max 100)" },
|
|
264
|
-
},
|
|
265
|
-
required: ["owner", "repo"],
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
name: "github_get_issue",
|
|
270
|
-
description: "Get a GitHub issue with full body and all comments. Use to understand a bug or feature request in depth.",
|
|
271
|
-
inputSchema: {
|
|
272
|
-
type: "object",
|
|
273
|
-
properties: {
|
|
274
|
-
owner: { type: "string", description: "Repo owner" },
|
|
275
|
-
repo: { type: "string", description: "Repository name" },
|
|
276
|
-
issue_number: { type: "number", description: "Issue number" },
|
|
277
|
-
},
|
|
278
|
-
required: ["owner", "repo", "issue_number"],
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
name: "github_get_file",
|
|
283
|
-
description: "Read a file from a GitHub repository. Returns decoded content (up to 10k chars). Use for reading code, configs, READMEs.",
|
|
284
|
-
inputSchema: {
|
|
285
|
-
type: "object",
|
|
286
|
-
properties: {
|
|
287
|
-
owner: { type: "string", description: "Repo owner" },
|
|
288
|
-
repo: { type: "string", description: "Repository name" },
|
|
289
|
-
path: { type: "string", description: "File path in the repo (e.g. src/index.ts, README.md)" },
|
|
290
|
-
ref: { type: "string", description: "Branch, tag, or commit SHA (default: main branch)" },
|
|
291
|
-
},
|
|
292
|
-
required: ["owner", "repo", "path"],
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
name: "github_get_commits",
|
|
297
|
-
description: "Get recent commits for a repo, branch, or specific file. Returns SHA, message, author, date.",
|
|
298
|
-
inputSchema: {
|
|
299
|
-
type: "object",
|
|
300
|
-
properties: {
|
|
301
|
-
owner: { type: "string", description: "Repo owner" },
|
|
302
|
-
repo: { type: "string", description: "Repository name" },
|
|
303
|
-
branch: { type: "string", description: "Branch name (default: repo default branch)" },
|
|
304
|
-
path: { type: "string", description: "Filter to commits touching a specific file path" },
|
|
305
|
-
per_page: { type: "number", description: "Number of commits (default 15, max 100)" },
|
|
306
|
-
},
|
|
307
|
-
required: ["owner", "repo"],
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
name: "github_search_code",
|
|
312
|
-
description: "Search code on GitHub. Supports qualifiers: repo:owner/repo, language:typescript, path:src/, filename:package.json, etc. " +
|
|
313
|
-
"Requires GITHUB_TOKEN for best results.",
|
|
314
|
-
inputSchema: {
|
|
315
|
-
type: "object",
|
|
316
|
-
properties: {
|
|
317
|
-
query: { type: "string", description: "Search query with optional qualifiers (e.g. 'useState repo:facebook/react language:typescript')" },
|
|
318
|
-
per_page: { type: "number", description: "Max results (default 10, max 30)" },
|
|
319
|
-
},
|
|
320
|
-
required: ["query"],
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
];
|
|
324
|
-
async function handleGithubTool(name, args) {
|
|
325
|
-
const a = (args ?? {});
|
|
326
|
-
switch (name) {
|
|
327
|
-
case "github_list_repos": {
|
|
328
|
-
const sort = a.sort ?? "updated";
|
|
329
|
-
const per_page = Math.min(a.per_page ?? 20, 100);
|
|
330
|
-
const ghPath = a.username
|
|
331
|
-
? `/users/${encodeURIComponent(a.username)}/repos?sort=${sort}&per_page=${per_page}`
|
|
332
|
-
: `/user/repos?type=owner&sort=${sort}&per_page=${per_page}`;
|
|
333
|
-
const repos = await gh(ghPath);
|
|
334
|
-
const who = a.username ?? "you";
|
|
335
|
-
const header = `## GitHub Repos — ${who} (${repos.length})\n`;
|
|
336
|
-
const rows = repos.map(r => {
|
|
337
|
-
const vis = r.private ? "🔒" : "📂";
|
|
338
|
-
const lang = r.language ? ` · ${r.language}` : "";
|
|
339
|
-
const desc = r.description ? `\n ${r.description.slice(0, 100)}` : "";
|
|
340
|
-
return `${vis} **[${r.full_name}](${r.html_url})** ⭐${r.stargazers_count} 🍴${r.forks_count}${lang} · ${fmtDate(r.updated_at)}${desc}`;
|
|
341
|
-
});
|
|
342
|
-
return md([header, ...rows].join("\n"), buildRepoList(a.username, repos));
|
|
343
|
-
}
|
|
344
|
-
case "github_list_prs": {
|
|
345
|
-
const { owner, repo, state = "open" } = a;
|
|
346
|
-
const per_page = Math.min(a.per_page ?? 15, 100);
|
|
347
|
-
const prs = await gh(`/repos/${owner}/${repo}/pulls?state=${state}&per_page=${per_page}`);
|
|
348
|
-
if (!prs.length)
|
|
349
|
-
return md(`No ${state} PRs in **${owner}/${repo}**.`, buildPrList(owner, repo, state, []));
|
|
350
|
-
const header = `## PRs — ${owner}/${repo} (${state}, ${prs.length})\n`;
|
|
351
|
-
const rows = prs.map(p => {
|
|
352
|
-
const draft = p.draft ? " `draft`" : "";
|
|
353
|
-
const changes = (p.additions != null)
|
|
354
|
-
? ` · \`+${p.additions}/-${p.deletions}\``
|
|
355
|
-
: "";
|
|
356
|
-
const body = p.body ? `\n ${p.body.slice(0, 120).replace(/\n/g, " ")}…` : "";
|
|
357
|
-
return `**#${p.number}** [${p.title}](${p.html_url})${draft}\n @${p.user?.login} · \`${p.head?.ref}\` → \`${p.base?.ref}\`${changes} · ${fmtDate(p.updated_at)}${body}`;
|
|
358
|
-
});
|
|
359
|
-
return md([header, ...rows].join("\n\n"), buildPrList(owner, repo, state, prs));
|
|
360
|
-
}
|
|
361
|
-
case "github_get_pr": {
|
|
362
|
-
const { owner, repo, pr_number } = a;
|
|
363
|
-
const [pr, files, reviews, comments] = await Promise.all([
|
|
364
|
-
gh(`/repos/${owner}/${repo}/pulls/${pr_number}`),
|
|
365
|
-
gh(`/repos/${owner}/${repo}/pulls/${pr_number}/files?per_page=50`),
|
|
366
|
-
gh(`/repos/${owner}/${repo}/pulls/${pr_number}/reviews`),
|
|
367
|
-
gh(`/repos/${owner}/${repo}/issues/${pr_number}/comments`),
|
|
368
|
-
]);
|
|
369
|
-
const stateEmoji = pr.state === "open" ? (pr.draft ? "🟡 Draft" : "🟢 Open") : "🟣 Merged";
|
|
370
|
-
const lines = [
|
|
371
|
-
`## PR #${pr.number} — ${pr.title}`,
|
|
372
|
-
`${stateEmoji} · @${pr.user?.login} · \`${pr.head?.ref}\` → \`${pr.base?.ref}\` · ${fmtDate(pr.updated_at)}`,
|
|
373
|
-
`\`+${pr.additions ?? 0}/-${pr.deletions ?? 0}\` across ${pr.changed_files ?? 0} files · [View on GitHub](${pr.html_url})`,
|
|
374
|
-
];
|
|
375
|
-
if (pr.body) {
|
|
376
|
-
lines.push("", "### Description", pr.body.slice(0, 1500));
|
|
377
|
-
}
|
|
378
|
-
if (files?.length) {
|
|
379
|
-
lines.push("", "### Changed Files");
|
|
380
|
-
for (const f of files.slice(0, 25)) {
|
|
381
|
-
const s = f.status === "added" ? "+" : f.status === "removed" ? "-" : "~";
|
|
382
|
-
lines.push(`\`${s}\` \`${f.filename}\` (+${f.additions}/-${f.deletions})`);
|
|
383
|
-
if (f.patch)
|
|
384
|
-
lines.push(`\`\`\`diff\n${f.patch.slice(0, 800)}\n\`\`\``);
|
|
385
|
-
}
|
|
386
|
-
if (files.length > 25)
|
|
387
|
-
lines.push(`_…and ${files.length - 25} more files_`);
|
|
388
|
-
}
|
|
389
|
-
if (reviews?.length) {
|
|
390
|
-
lines.push("", "### Reviews");
|
|
391
|
-
for (const r of reviews) {
|
|
392
|
-
if (!r.body && r.state === "COMMENTED")
|
|
393
|
-
continue;
|
|
394
|
-
lines.push(`**@${r.user?.login}** ${r.state} · ${fmtDate(r.submitted_at)}${r.body ? `\n> ${r.body.slice(0, 300)}` : ""}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (comments?.length) {
|
|
398
|
-
lines.push("", "### Comments");
|
|
399
|
-
for (const c of comments.slice(0, 8)) {
|
|
400
|
-
lines.push(`**@${c.user?.login}** · ${fmtDate(c.created_at)}\n> ${c.body?.slice(0, 400)}`);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return md(lines.join("\n"), buildPrDetail(pr, files, reviews, comments));
|
|
404
|
-
}
|
|
405
|
-
case "github_list_issues": {
|
|
406
|
-
const { owner, repo, state = "open", labels } = a;
|
|
407
|
-
const per_page = Math.min(a.per_page ?? 15, 100);
|
|
408
|
-
let issuePath = `/repos/${owner}/${repo}/issues?state=${state}&per_page=${per_page}`;
|
|
409
|
-
if (labels)
|
|
410
|
-
issuePath += `&labels=${encodeURIComponent(labels)}`;
|
|
411
|
-
const all = await gh(issuePath);
|
|
412
|
-
const issues = all.filter((i) => !i.pull_request);
|
|
413
|
-
if (!issues.length)
|
|
414
|
-
return md(`No ${state} issues in **${owner}/${repo}**${labels ? ` with labels: ${labels}` : ""}.`, buildIssueList(owner, repo, state, []));
|
|
415
|
-
const header = `## Issues — ${owner}/${repo} (${state}, ${issues.length})\n`;
|
|
416
|
-
const rows = issues.map((i) => {
|
|
417
|
-
const lbls = i.labels?.length ? ` · ${i.labels.map((l) => `\`${l.name}\``).join(" ")}` : "";
|
|
418
|
-
const cmts = i.comments ? ` · 💬${i.comments}` : "";
|
|
419
|
-
const body = i.body ? `\n ${i.body.slice(0, 100).replace(/\n/g, " ")}…` : "";
|
|
420
|
-
return `**#${i.number}** [${i.title}](${i.html_url})\n @${i.user?.login}${lbls}${cmts} · ${fmtDate(i.updated_at)}${body}`;
|
|
421
|
-
});
|
|
422
|
-
return md([header, ...rows].join("\n\n"), buildIssueList(owner, repo, state, issues));
|
|
423
|
-
}
|
|
424
|
-
case "github_get_issue": {
|
|
425
|
-
const { owner, repo, issue_number } = a;
|
|
426
|
-
const [issue, comments] = await Promise.all([
|
|
427
|
-
gh(`/repos/${owner}/${repo}/issues/${issue_number}`),
|
|
428
|
-
gh(`/repos/${owner}/${repo}/issues/${issue_number}/comments?per_page=20`),
|
|
429
|
-
]);
|
|
430
|
-
const lbls = issue.labels?.length ? issue.labels.map((l) => `\`${l.name}\``).join(" ") : "";
|
|
431
|
-
const assignees = issue.assignees?.length ? `Assigned: @${issue.assignees.map((a) => a.login).join(", @")}` : "";
|
|
432
|
-
const lines = [
|
|
433
|
-
`## Issue #${issue.number} — ${issue.title}`,
|
|
434
|
-
`${issue.state === "open" ? "🟢 Open" : "🔴 Closed"} · @${issue.user?.login} · ${fmtDate(issue.created_at)} · [View on GitHub](${issue.html_url})`,
|
|
435
|
-
];
|
|
436
|
-
if (lbls)
|
|
437
|
-
lines.push(lbls);
|
|
438
|
-
if (assignees)
|
|
439
|
-
lines.push(assignees);
|
|
440
|
-
if (issue.body)
|
|
441
|
-
lines.push("", issue.body.slice(0, 3000));
|
|
442
|
-
if (comments?.length) {
|
|
443
|
-
lines.push("", `---`, "", `### Comments (${comments.length})`);
|
|
444
|
-
for (const c of comments) {
|
|
445
|
-
lines.push(`**@${c.user?.login}** · ${fmtDate(c.created_at)}`);
|
|
446
|
-
lines.push(c.body?.slice(0, 1000) ?? "");
|
|
447
|
-
lines.push("");
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return md(lines.join("\n"), buildIssueDetail(issue, comments));
|
|
451
|
-
}
|
|
452
|
-
case "github_get_file": {
|
|
453
|
-
const { owner, repo, path: filePath, ref } = a;
|
|
454
|
-
let fileUrl = `/repos/${owner}/${repo}/contents/${filePath}`;
|
|
455
|
-
if (ref)
|
|
456
|
-
fileUrl += `?ref=${encodeURIComponent(ref)}`;
|
|
457
|
-
const data = await gh(fileUrl);
|
|
458
|
-
if (data.type !== "file") {
|
|
459
|
-
return md(`Not a file — got type \`${data.type}\`. Specify a full file path, not a directory.`, { owner, repo, path: filePath, isFile: false });
|
|
460
|
-
}
|
|
461
|
-
const content = Buffer.from(data.content.replace(/\n/g, ""), "base64").toString("utf-8");
|
|
462
|
-
const truncated = content.length > 10000;
|
|
463
|
-
const lang = langExt(filePath);
|
|
464
|
-
const lines = [
|
|
465
|
-
`## \`${data.path}\` — ${owner}/${repo}${ref ? ` @ ${ref}` : ""}`,
|
|
466
|
-
`Size: ${(data.size / 1024).toFixed(1)}KB · SHA: \`${data.sha?.slice(0, 8)}\` · [View on GitHub](${data.html_url})`,
|
|
467
|
-
"",
|
|
468
|
-
`\`\`\`${lang}`,
|
|
469
|
-
content.slice(0, 10000),
|
|
470
|
-
"```",
|
|
471
|
-
];
|
|
472
|
-
if (truncated)
|
|
473
|
-
lines.push(`\n_File truncated at 10,000 chars (${(content.length / 1024).toFixed(1)}KB total)._`);
|
|
474
|
-
return md(lines.join("\n"), {
|
|
475
|
-
owner, repo, path: data.path, ref: ref ?? null, isFile: true,
|
|
476
|
-
sizeBytes: data.size ?? null, sha: data.sha ?? null, url: data.html_url ?? null,
|
|
477
|
-
language: lang, truncated, content: content.slice(0, 10000),
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
case "github_get_commits": {
|
|
481
|
-
const { owner, repo, branch, path: filePath } = a;
|
|
482
|
-
const per_page = Math.min(a.per_page ?? 15, 100);
|
|
483
|
-
let commitUrl = `/repos/${owner}/${repo}/commits?per_page=${per_page}`;
|
|
484
|
-
if (branch)
|
|
485
|
-
commitUrl += `&sha=${encodeURIComponent(branch)}`;
|
|
486
|
-
if (filePath)
|
|
487
|
-
commitUrl += `&path=${encodeURIComponent(filePath)}`;
|
|
488
|
-
const commits = await gh(commitUrl);
|
|
489
|
-
if (!commits.length)
|
|
490
|
-
return md(`No commits found in **${owner}/${repo}**${branch ? ` @ ${branch}` : ""}${filePath ? ` for \`${filePath}\`` : ""}.`, buildCommitList(owner, repo, branch, filePath, []));
|
|
491
|
-
const filter = filePath ? ` · \`${filePath}\`` : "";
|
|
492
|
-
const header = `## Commits — ${owner}/${repo}${branch ? ` @ ${branch}` : ""}${filter} (${commits.length})\n`;
|
|
493
|
-
const rows = commits.map(c => {
|
|
494
|
-
const msg = c.commit?.message?.split("\n")[0].slice(0, 100) ?? "(no message)";
|
|
495
|
-
const author = c.commit?.author?.name ?? c.author?.login ?? "unknown";
|
|
496
|
-
const sha = c.sha?.slice(0, 7);
|
|
497
|
-
return `\`${sha}\` **${msg}**\n @${author} · ${fmtDate(c.commit?.author?.date)} · [↗](${c.html_url})`;
|
|
498
|
-
});
|
|
499
|
-
return md([header, ...rows].join("\n\n"), buildCommitList(owner, repo, branch, filePath, commits));
|
|
500
|
-
}
|
|
501
|
-
case "github_search_code": {
|
|
502
|
-
// GitHub's code-search endpoint requires authentication. Without
|
|
503
|
-
// GITHUB_TOKEN it returns 422 with a non-obvious message. Detect that
|
|
504
|
-
// case upfront and give the user a concrete setup path instead of an
|
|
505
|
-
// opaque error.
|
|
506
|
-
if (!process.env.GITHUB_TOKEN) {
|
|
507
|
-
return {
|
|
508
|
-
content: [{
|
|
509
|
-
type: "text",
|
|
510
|
-
text: [
|
|
511
|
-
`❌ **github_search_code requires authentication**`,
|
|
512
|
-
``,
|
|
513
|
-
`GitHub's code-search API does not work for anonymous callers - they return 422.`,
|
|
514
|
-
`Other \`github_*\` tools work without a token (rate-limited to 60 req/hr).`,
|
|
515
|
-
``,
|
|
516
|
-
`**Setup (one-time):**`,
|
|
517
|
-
``,
|
|
518
|
-
`1. Create a personal access token: https://github.com/settings/tokens`,
|
|
519
|
-
`2. Minimum scopes needed: \`public_repo\` (or \`repo\` for private)`,
|
|
520
|
-
`3. Set the env var in your MCP client config:`,
|
|
521
|
-
``,
|
|
522
|
-
` \`\`\`json`,
|
|
523
|
-
` {`,
|
|
524
|
-
` "mcpServers": {`,
|
|
525
|
-
` "finch": {`,
|
|
526
|
-
` "command": "npx",`,
|
|
527
|
-
` "args": ["-y", "@finchagentic/mcp"],`,
|
|
528
|
-
` "env": { "GITHUB_TOKEN": "ghp_..." }`,
|
|
529
|
-
` }`,
|
|
530
|
-
` }`,
|
|
531
|
-
` }`,
|
|
532
|
-
` \`\`\``,
|
|
533
|
-
``,
|
|
534
|
-
`4. Restart the MCP client.`,
|
|
535
|
-
``,
|
|
536
|
-
`Authenticated rate limit: 5,000 req/hr (vs 60 unauthenticated for non-search endpoints).`,
|
|
537
|
-
].join("\n"),
|
|
538
|
-
}],
|
|
539
|
-
isError: true,
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
const { query } = a;
|
|
543
|
-
const per_page = Math.min(a.per_page ?? 10, 30);
|
|
544
|
-
try {
|
|
545
|
-
const data = await gh(`/search/code?q=${encodeURIComponent(query)}&per_page=${per_page}`);
|
|
546
|
-
const items = data.items ?? [];
|
|
547
|
-
const header = `## Code Search — \`${query}\`\n${data.total_count?.toLocaleString()} total results (showing ${items.length})\n`;
|
|
548
|
-
const rows = items.map((item) => `**[\`${item.path}\`](${item.html_url})** · ${item.repository?.full_name}`);
|
|
549
|
-
return md([header, ...rows].join("\n"), buildCodeSearch(query, data.total_count, items));
|
|
550
|
-
}
|
|
551
|
-
catch (e) {
|
|
552
|
-
return {
|
|
553
|
-
content: [{
|
|
554
|
-
type: "text",
|
|
555
|
-
text: `github_search_code failed: ${e.message}\n\nIf this says "Unprocessable Entity" or 422, your GITHUB_TOKEN may lack required scopes - needs at least \`public_repo\`.`,
|
|
556
|
-
}],
|
|
557
|
-
isError: true,
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
default:
|
|
562
|
-
return null;
|
|
563
|
-
}
|
|
564
|
-
}
|