@oss-scout/mcp 0.2.0 → 0.3.0
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/LICENSE +21 -0
- package/dist/index.js +8 -8
- package/dist/mcp-server.bundle.cjs +725 -290
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +21 -15
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +215 -36
- package/package.json +18 -13
package/dist/resources.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { McpServer } from
|
|
2
|
-
import type { OssScout } from
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { OssScout } from "@oss-scout/core";
|
|
3
3
|
export declare function registerResources(server: McpServer, scout: OssScout): void;
|
package/dist/resources.js
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
export function registerResources(server, scout) {
|
|
2
|
-
server.resource(
|
|
3
|
-
contents: [
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
server.resource("config", "scout://config", { description: "Current oss-scout preferences" }, async () => ({
|
|
3
|
+
contents: [
|
|
4
|
+
{
|
|
5
|
+
uri: "scout://config",
|
|
6
|
+
mimeType: "application/json",
|
|
6
7
|
text: JSON.stringify(scout.getPreferences(), null, 2),
|
|
7
|
-
}
|
|
8
|
+
},
|
|
9
|
+
],
|
|
8
10
|
}));
|
|
9
|
-
server.resource(
|
|
10
|
-
contents: [
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
server.resource("results", "scout://results", { description: "Saved search results from previous runs" }, async () => ({
|
|
12
|
+
contents: [
|
|
13
|
+
{
|
|
14
|
+
uri: "scout://results",
|
|
15
|
+
mimeType: "application/json",
|
|
13
16
|
text: JSON.stringify(scout.getSavedResults(), null, 2),
|
|
14
|
-
}
|
|
17
|
+
},
|
|
18
|
+
],
|
|
15
19
|
}));
|
|
16
|
-
server.resource(
|
|
20
|
+
server.resource("scores", "scout://scores", { description: "Repository score data based on contribution history" }, async () => {
|
|
17
21
|
const state = scout.getState();
|
|
18
22
|
return {
|
|
19
|
-
contents: [
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
contents: [
|
|
24
|
+
{
|
|
25
|
+
uri: "scout://scores",
|
|
26
|
+
mimeType: "application/json",
|
|
22
27
|
text: JSON.stringify(state.repoScores, null, 2),
|
|
23
|
-
}
|
|
28
|
+
},
|
|
29
|
+
],
|
|
24
30
|
};
|
|
25
31
|
});
|
|
26
32
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { McpServer } from
|
|
2
|
-
import type { OssScout } from
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { OssScout } from "@oss-scout/core";
|
|
3
3
|
export declare function registerTools(server: McpServer, scout: OssScout): void;
|
package/dist/tools.js
CHANGED
|
@@ -1,43 +1,204 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { SearchStrategySchema, ScoutPreferencesSchema } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SearchStrategySchema, ScoutPreferencesSchema } from "@oss-scout/core";
|
|
3
|
+
const TOOL_TIMEOUT_MS = 60000;
|
|
4
|
+
function withTimeout(promise, ms = TOOL_TIMEOUT_MS) {
|
|
5
|
+
let timeoutId;
|
|
6
|
+
const timer = new Promise((_, reject) => {
|
|
7
|
+
timeoutId = setTimeout(() => reject(new Error(`Request timed out after ${ms / 1000}s`)), ms);
|
|
8
|
+
});
|
|
9
|
+
return Promise.race([promise, timer]).finally(() => clearTimeout(timeoutId));
|
|
10
|
+
}
|
|
3
11
|
export function registerTools(server, scout) {
|
|
4
|
-
server.tool(
|
|
5
|
-
maxResults: z
|
|
6
|
-
|
|
12
|
+
server.tool("search", "Search for open source issues matching your preferences", {
|
|
13
|
+
maxResults: z
|
|
14
|
+
.number()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Maximum number of results to return (default 10)"),
|
|
17
|
+
strategies: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Comma-separated search strategies: merged, starred, broad, maintained, all"),
|
|
7
21
|
}, async ({ maxResults, strategies }) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
try {
|
|
23
|
+
const parsedStrategies = strategies
|
|
24
|
+
? strategies
|
|
25
|
+
.split(",")
|
|
26
|
+
.map((s) => SearchStrategySchema.parse(s.trim()))
|
|
27
|
+
: undefined;
|
|
28
|
+
const result = await withTimeout(scout.search({
|
|
29
|
+
maxResults: maxResults ?? 10,
|
|
30
|
+
strategies: parsedStrategies,
|
|
31
|
+
}));
|
|
32
|
+
scout.saveResults(result.candidates);
|
|
33
|
+
await scout.checkpoint();
|
|
34
|
+
return {
|
|
35
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
16
45
|
});
|
|
17
|
-
server.tool(
|
|
18
|
-
issueUrl: z
|
|
46
|
+
server.tool("vet", "Vet a specific GitHub issue for contribution viability", {
|
|
47
|
+
issueUrl: z
|
|
48
|
+
.string()
|
|
49
|
+
.describe("Full GitHub issue URL (e.g. https://github.com/owner/repo/issues/123)"),
|
|
19
50
|
}, async ({ issueUrl }) => {
|
|
20
|
-
|
|
21
|
-
|
|
51
|
+
try {
|
|
52
|
+
const candidate = await withTimeout(scout.vetIssue(issueUrl));
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: JSON.stringify(candidate, null, 2) }],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
22
64
|
});
|
|
23
|
-
server.tool(
|
|
65
|
+
server.tool("config", "Show current oss-scout configuration preferences", async () => {
|
|
24
66
|
const preferences = scout.getPreferences();
|
|
25
|
-
return {
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: JSON.stringify(preferences, null, 2) }],
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
server.tool("skip", "Skip, unskip, list, or clear skipped issues. Skipped issues are excluded from future searches and auto-expire after 90 days.", {
|
|
72
|
+
action: z
|
|
73
|
+
.enum(["add", "remove", "list", "clear"])
|
|
74
|
+
.default("add")
|
|
75
|
+
.describe("Action to perform (default: add)"),
|
|
76
|
+
issueUrl: z
|
|
77
|
+
.string()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("GitHub issue URL (required for add/remove actions)"),
|
|
80
|
+
}, async ({ action, issueUrl }) => {
|
|
81
|
+
try {
|
|
82
|
+
if ((action === "add" || action === "remove") && !issueUrl) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: `Error: issueUrl is required for the "${action}" action.`,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
isError: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (action === "list") {
|
|
94
|
+
const skipped = scout.getSkippedIssues();
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: JSON.stringify(skipped, null, 2) }],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (action === "clear") {
|
|
100
|
+
const count = scout.getSkippedIssues().length;
|
|
101
|
+
scout.clearSkippedIssues();
|
|
102
|
+
const synced = await scout.checkpoint();
|
|
103
|
+
const syncNote = synced ? "" : " (saved locally, gist sync failed)";
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: count > 0
|
|
109
|
+
? `Skip list cleared (${count} entries removed)${syncNote}`
|
|
110
|
+
: `Skip list already empty${syncNote}`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (action === "remove") {
|
|
116
|
+
const wasPresent = scout
|
|
117
|
+
.getSkippedIssues()
|
|
118
|
+
.some((s) => s.url === issueUrl);
|
|
119
|
+
scout.unskipIssue(issueUrl);
|
|
120
|
+
const synced = await scout.checkpoint();
|
|
121
|
+
const syncNote = synced ? "" : " (saved locally, gist sync failed)";
|
|
122
|
+
return {
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: "text",
|
|
126
|
+
text: wasPresent
|
|
127
|
+
? `Removed from skip list: ${issueUrl}${syncNote}`
|
|
128
|
+
: `Not in skip list: ${issueUrl}`,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// action === "add"
|
|
134
|
+
const alreadySkipped = scout
|
|
135
|
+
.getSkippedIssues()
|
|
136
|
+
.some((s) => s.url === issueUrl);
|
|
137
|
+
if (alreadySkipped) {
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{ type: "text", text: `Already in skip list: ${issueUrl}` },
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const saved = scout
|
|
145
|
+
.getSavedResults()
|
|
146
|
+
.find((r) => r.issueUrl === issueUrl);
|
|
147
|
+
const metadata = saved
|
|
148
|
+
? {
|
|
149
|
+
repo: saved.repo,
|
|
150
|
+
number: saved.number,
|
|
151
|
+
title: saved.title,
|
|
152
|
+
}
|
|
153
|
+
: undefined;
|
|
154
|
+
scout.skipIssue(issueUrl, metadata);
|
|
155
|
+
const synced = await scout.checkpoint();
|
|
156
|
+
const syncNote = synced ? "" : " (saved locally, gist sync failed)";
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: "text", text: `Skipped: ${issueUrl}${syncNote}` }],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
165
|
+
isError: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
26
168
|
});
|
|
27
169
|
const VALID_KEYS = Object.keys(ScoutPreferencesSchema.shape);
|
|
28
170
|
const ARRAY_KEYS = new Set([
|
|
29
|
-
|
|
30
|
-
|
|
171
|
+
"languages",
|
|
172
|
+
"labels",
|
|
173
|
+
"projectCategories",
|
|
174
|
+
"excludeRepos",
|
|
175
|
+
"excludeOrgs",
|
|
176
|
+
"aiPolicyBlocklist",
|
|
177
|
+
"scope",
|
|
178
|
+
"defaultStrategy",
|
|
179
|
+
]);
|
|
180
|
+
const NUMBER_KEYS = new Set([
|
|
181
|
+
"minStars",
|
|
182
|
+
"maxIssueAgeDays",
|
|
183
|
+
"minRepoScoreThreshold",
|
|
31
184
|
]);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
185
|
+
const BOOLEAN_KEYS = new Set(["includeDocIssues"]);
|
|
186
|
+
server.tool("config-set", "Update an oss-scout configuration preference", {
|
|
187
|
+
key: z
|
|
188
|
+
.string()
|
|
189
|
+
.describe("Preference key to update (e.g. languages, minStars, excludeRepos)"),
|
|
190
|
+
value: z
|
|
191
|
+
.string()
|
|
192
|
+
.describe("New value — comma-separated for arrays, plain string for scalars"),
|
|
37
193
|
}, async ({ key, value }) => {
|
|
38
194
|
if (!VALID_KEYS.includes(key)) {
|
|
39
195
|
return {
|
|
40
|
-
content: [
|
|
196
|
+
content: [
|
|
197
|
+
{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: `Unknown config key: "${key}". Valid keys: ${VALID_KEYS.join(", ")}`,
|
|
200
|
+
},
|
|
201
|
+
],
|
|
41
202
|
isError: true,
|
|
42
203
|
};
|
|
43
204
|
}
|
|
@@ -47,18 +208,26 @@ export function registerTools(server, scout) {
|
|
|
47
208
|
try {
|
|
48
209
|
parsed = JSON.parse(value);
|
|
49
210
|
if (!Array.isArray(parsed)) {
|
|
50
|
-
parsed = value
|
|
211
|
+
parsed = value
|
|
212
|
+
.split(",")
|
|
213
|
+
.map((s) => s.trim())
|
|
214
|
+
.filter(Boolean);
|
|
51
215
|
}
|
|
52
216
|
}
|
|
53
217
|
catch {
|
|
54
|
-
parsed = value
|
|
218
|
+
parsed = value
|
|
219
|
+
.split(",")
|
|
220
|
+
.map((s) => s.trim())
|
|
221
|
+
.filter(Boolean);
|
|
55
222
|
}
|
|
56
223
|
}
|
|
57
224
|
else if (NUMBER_KEYS.has(key)) {
|
|
58
225
|
const num = Number(value);
|
|
59
226
|
if (isNaN(num)) {
|
|
60
227
|
return {
|
|
61
|
-
content: [
|
|
228
|
+
content: [
|
|
229
|
+
{ type: "text", text: `Invalid number for "${key}": "${value}"` },
|
|
230
|
+
],
|
|
62
231
|
isError: true,
|
|
63
232
|
};
|
|
64
233
|
}
|
|
@@ -66,13 +235,18 @@ export function registerTools(server, scout) {
|
|
|
66
235
|
}
|
|
67
236
|
else if (BOOLEAN_KEYS.has(key)) {
|
|
68
237
|
const lower = value.toLowerCase();
|
|
69
|
-
if (lower ===
|
|
238
|
+
if (lower === "true" || lower === "yes")
|
|
70
239
|
parsed = true;
|
|
71
|
-
else if (lower ===
|
|
240
|
+
else if (lower === "false" || lower === "no")
|
|
72
241
|
parsed = false;
|
|
73
242
|
else {
|
|
74
243
|
return {
|
|
75
|
-
content: [
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: `Invalid boolean for "${key}": "${value}". Use true/false or yes/no.`,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
76
250
|
isError: true,
|
|
77
251
|
};
|
|
78
252
|
}
|
|
@@ -86,14 +260,19 @@ export function registerTools(server, scout) {
|
|
|
86
260
|
// Validate the full preferences object with Zod
|
|
87
261
|
const result = ScoutPreferencesSchema.safeParse(candidate);
|
|
88
262
|
if (!result.success) {
|
|
89
|
-
const issues = result.error.issues
|
|
263
|
+
const issues = result.error.issues
|
|
264
|
+
.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
265
|
+
.join("; ");
|
|
90
266
|
return {
|
|
91
|
-
content: [{ type:
|
|
267
|
+
content: [{ type: "text", text: `Validation error: ${issues}` }],
|
|
92
268
|
isError: true,
|
|
93
269
|
};
|
|
94
270
|
}
|
|
95
271
|
scout.updatePreferences({ [key]: parsed });
|
|
272
|
+
await scout.checkpoint();
|
|
96
273
|
const updated = scout.getPreferences();
|
|
97
|
-
return {
|
|
274
|
+
return {
|
|
275
|
+
content: [{ type: "text", text: JSON.stringify(updated, null, 2) }],
|
|
276
|
+
};
|
|
98
277
|
});
|
|
99
278
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-scout/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for oss-scout — exposes issue discovery and vetting as MCP tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,19 +15,10 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"dist/"
|
|
17
17
|
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc",
|
|
20
|
-
"bundle": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/mcp-server.bundle.cjs",
|
|
21
|
-
"test": "vitest run",
|
|
22
|
-
"test:watch": "vitest",
|
|
23
|
-
"test:coverage": "vitest run --coverage",
|
|
24
|
-
"typecheck": "tsc --noEmit",
|
|
25
|
-
"prepublishOnly": "pnpm run build && pnpm run bundle"
|
|
26
|
-
},
|
|
27
18
|
"dependencies": {
|
|
28
19
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
20
|
+
"zod": "^4.3.6",
|
|
21
|
+
"@oss-scout/core": "^0.3.0"
|
|
31
22
|
},
|
|
32
23
|
"devDependencies": {
|
|
33
24
|
"@types/node": "^25.5.0",
|
|
@@ -37,6 +28,12 @@
|
|
|
37
28
|
"@vitest/coverage-v8": "^4.1.0",
|
|
38
29
|
"vitest": "^4.1.0"
|
|
39
30
|
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"mcp-server",
|
|
34
|
+
"claude",
|
|
35
|
+
"issue-discovery"
|
|
36
|
+
],
|
|
40
37
|
"engines": {
|
|
41
38
|
"node": ">=20"
|
|
42
39
|
},
|
|
@@ -45,5 +42,13 @@
|
|
|
45
42
|
"type": "git",
|
|
46
43
|
"url": "git+https://github.com/costajohnt/oss-scout.git",
|
|
47
44
|
"directory": "packages/mcp-server"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc",
|
|
48
|
+
"bundle": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/mcp-server.bundle.cjs",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"test:coverage": "vitest run --coverage",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
48
53
|
}
|
|
49
|
-
}
|
|
54
|
+
}
|