@oss-scout/mcp 0.10.1 → 0.10.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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-server.bundle.cjs +74 -12
- package/dist/tools.js +5 -2
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { registerResources } from "./resources.js";
|
|
|
12
12
|
/** Server identity, kept in one place for the bin and any embedding host. */
|
|
13
13
|
export declare const SERVER_INFO: {
|
|
14
14
|
readonly name: "oss-scout-mcp";
|
|
15
|
-
readonly version: "0.10.
|
|
15
|
+
readonly version: "0.10.2";
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
18
|
* Build a fully-wired MCP server (tools + resources) around a scout.
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { registerResources } from "./resources.js";
|
|
|
15
15
|
/** Server identity, kept in one place for the bin and any embedding host. */
|
|
16
16
|
export const SERVER_INFO = {
|
|
17
17
|
name: "oss-scout-mcp",
|
|
18
|
-
version: "0.10.
|
|
18
|
+
version: "0.10.2", // x-release-please-version
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
21
|
* Build a fully-wired MCP server (tools + resources) around a scout.
|
|
@@ -37684,7 +37684,11 @@ async function triageWithSLM(input, options) {
|
|
|
37684
37684
|
format: SLM_TRIAGE_SCHEMA,
|
|
37685
37685
|
options: { temperature: 0.1 }
|
|
37686
37686
|
}),
|
|
37687
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
37687
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
37688
|
+
// No redirect following: an allowed private host could otherwise
|
|
37689
|
+
// 307/308-redirect the issue-text POST body to a public origin (#310).
|
|
37690
|
+
// Ollama never redirects, so this costs nothing.
|
|
37691
|
+
redirect: "error"
|
|
37688
37692
|
});
|
|
37689
37693
|
} catch {
|
|
37690
37694
|
return null;
|
|
@@ -38079,6 +38083,9 @@ var IssueVetter = class {
|
|
|
38079
38083
|
let rateLimitFailures = 0;
|
|
38080
38084
|
let attemptedCount = 0;
|
|
38081
38085
|
let firstAuthError = null;
|
|
38086
|
+
let firstForbiddenError = null;
|
|
38087
|
+
let forbiddenCount = 0;
|
|
38088
|
+
const forbiddenRepos = /* @__PURE__ */ new Set();
|
|
38082
38089
|
const uniqueUrls = [...new Set(urls)];
|
|
38083
38090
|
const issueRefs = uniqueUrls.flatMap((url2) => {
|
|
38084
38091
|
const p = parseGitHubUrl(url2);
|
|
@@ -38097,6 +38104,12 @@ var IssueVetter = class {
|
|
|
38097
38104
|
break;
|
|
38098
38105
|
if (firstAuthError)
|
|
38099
38106
|
break;
|
|
38107
|
+
const parsedUrl = parseGitHubUrl(url2);
|
|
38108
|
+
const repoKey = parsedUrl ? `${parsedUrl.owner}/${parsedUrl.repo}` : null;
|
|
38109
|
+
if (repoKey && forbiddenRepos.has(repoKey)) {
|
|
38110
|
+
debug(MODULE10, `Skipping ${url2}: repo already returned 403`);
|
|
38111
|
+
continue;
|
|
38112
|
+
}
|
|
38100
38113
|
attemptedCount++;
|
|
38101
38114
|
const core = prefetchFor(url2);
|
|
38102
38115
|
const task = this.vetIssue(url2, core ? { prefetched: core } : void 0).then((candidate) => {
|
|
@@ -38107,10 +38120,19 @@ var IssueVetter = class {
|
|
|
38107
38120
|
candidates.push(candidate);
|
|
38108
38121
|
}
|
|
38109
38122
|
}).catch((error48) => {
|
|
38110
|
-
if (
|
|
38123
|
+
if (getHttpStatusCode(error48) === 401) {
|
|
38111
38124
|
firstAuthError ??= error48;
|
|
38112
38125
|
return;
|
|
38113
38126
|
}
|
|
38127
|
+
if (isAuthError(error48)) {
|
|
38128
|
+
firstForbiddenError ??= error48;
|
|
38129
|
+
forbiddenCount++;
|
|
38130
|
+
failedVettingCount++;
|
|
38131
|
+
if (repoKey)
|
|
38132
|
+
forbiddenRepos.add(repoKey);
|
|
38133
|
+
warn(MODULE10, `Access forbidden for ${url2} (SAML/token scope?) \u2014 skipping this repo:`, errorMessage(error48));
|
|
38134
|
+
return;
|
|
38135
|
+
}
|
|
38114
38136
|
failedVettingCount++;
|
|
38115
38137
|
if (isRateLimitError(error48)) {
|
|
38116
38138
|
rateLimitFailures++;
|
|
@@ -38129,6 +38151,9 @@ var IssueVetter = class {
|
|
|
38129
38151
|
}
|
|
38130
38152
|
throw firstAuthError;
|
|
38131
38153
|
}
|
|
38154
|
+
if (firstForbiddenError && attemptedCount > 0 && forbiddenCount === attemptedCount) {
|
|
38155
|
+
throw firstForbiddenError;
|
|
38156
|
+
}
|
|
38132
38157
|
const allFailed = failedVettingCount === attemptedCount && attemptedCount > 0;
|
|
38133
38158
|
if (allFailed) {
|
|
38134
38159
|
warn(MODULE10, `All ${attemptedCount} issue(s) failed vetting. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
@@ -38806,6 +38831,7 @@ var IssueDiscovery = class {
|
|
|
38806
38831
|
resetGraphQLSearchQueryCount();
|
|
38807
38832
|
const tracker = this.budgetTracker;
|
|
38808
38833
|
let searchBudget = CRITICAL_BUDGET_THRESHOLD - 1;
|
|
38834
|
+
let preflightFailed = false;
|
|
38809
38835
|
try {
|
|
38810
38836
|
const rateLimit = await checkRateLimit(this.githubToken);
|
|
38811
38837
|
searchBudget = rateLimit.remaining;
|
|
@@ -38823,6 +38849,7 @@ var IssueDiscovery = class {
|
|
|
38823
38849
|
} catch (error48) {
|
|
38824
38850
|
if (getHttpStatusCode(error48) === 401)
|
|
38825
38851
|
throw error48;
|
|
38852
|
+
preflightFailed = true;
|
|
38826
38853
|
tracker.init(CRITICAL_BUDGET_THRESHOLD, new Date(Date.now() + 6e4).toISOString());
|
|
38827
38854
|
warn(MODULE12, "Could not check rate limit \u2014 using conservative budget, skipping the starred phase:", errorMessage(error48));
|
|
38828
38855
|
}
|
|
@@ -38926,7 +38953,7 @@ var IssueDiscovery = class {
|
|
|
38926
38953
|
strategiesUsed.push("maintained");
|
|
38927
38954
|
}
|
|
38928
38955
|
const phasesSkippedForBudget = searchBudget < CRITICAL_BUDGET_THRESHOLD;
|
|
38929
|
-
const budgetNote = phasesSkippedForBudget ? ` The starred-repo phase was skipped due to critically low API quota (${searchBudget} remaining); broad and maintained phases still ran on GraphQL.` : "";
|
|
38956
|
+
const budgetNote = phasesSkippedForBudget ? preflightFailed ? " The starred-repo phase was skipped as a precaution because the rate-limit check failed (quota unknown); broad and maintained phases still ran on GraphQL." : ` The starred-repo phase was skipped due to critically low API quota (${searchBudget} remaining); broad and maintained phases still ran on GraphQL.` : "";
|
|
38930
38957
|
if (allCandidates.length === 0) {
|
|
38931
38958
|
const errorDetails = [
|
|
38932
38959
|
phaseErrors["0"] ? `Phase 0 (merged-PR repos): ${phaseErrors["0"]}` : null,
|
|
@@ -39034,10 +39061,17 @@ function parseRoadmapIssueRefs(content, owner, repo) {
|
|
|
39034
39061
|
refs.add(n);
|
|
39035
39062
|
}
|
|
39036
39063
|
const fullRefStripPattern = /\b[\w.-]+\/[\w.-]+#\d+\b/gi;
|
|
39064
|
+
let inFence = false;
|
|
39037
39065
|
for (const line of content.split("\n")) {
|
|
39066
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
39067
|
+
inFence = !inFence;
|
|
39068
|
+
continue;
|
|
39069
|
+
}
|
|
39070
|
+
if (inFence)
|
|
39071
|
+
continue;
|
|
39038
39072
|
if (/^\s*#+\s/.test(line))
|
|
39039
39073
|
continue;
|
|
39040
|
-
const stripped = line.replace(fullRefStripPattern, "");
|
|
39074
|
+
const stripped = line.replace(fullRefStripPattern, "").replace(/`[^`]*`/g, "");
|
|
39041
39075
|
for (const m of stripped.matchAll(/(?:^|[^&\w])#(\d+)\b/g)) {
|
|
39042
39076
|
const n = Number.parseInt(m[1], 10);
|
|
39043
39077
|
if (n > 0)
|
|
@@ -39114,7 +39148,8 @@ function classifyHorizon(input) {
|
|
|
39114
39148
|
function splitByHorizon(candidates, count, ratio = DEFAULT_SPLIT_RATIO) {
|
|
39115
39149
|
const allQuick = candidates.filter((c) => c.horizon === "quick-win").sort((a, b) => b.viabilityScore - a.viabilityScore);
|
|
39116
39150
|
const allBigger = candidates.filter((c) => c.horizon === "bigger-bet").sort((a, b) => b.viabilityScore - a.viabilityScore);
|
|
39117
|
-
const
|
|
39151
|
+
const clampedRatio = Math.max(0, Math.min(1, ratio));
|
|
39152
|
+
const targetQuick = Math.round(count * clampedRatio);
|
|
39118
39153
|
const targetBigger = count - targetQuick;
|
|
39119
39154
|
const quickTaken = Math.min(allQuick.length, targetQuick);
|
|
39120
39155
|
const biggerTaken = Math.min(allBigger.length, targetBigger);
|
|
@@ -39168,6 +39203,17 @@ function detectWontfixNoContributor(input) {
|
|
|
39168
39203
|
}
|
|
39169
39204
|
var NO_ANCHORS_MESSAGE = "No anchor repos yet (need 3+ merged PRs in a repo). Try `scout search` to build relationships first.";
|
|
39170
39205
|
var NO_RESULTS_MESSAGE = "No open feature opportunities in your anchor repos right now. Check back next week, or try `scout search` for fix-mode work.";
|
|
39206
|
+
function buildRepoExclusionFilter(opts) {
|
|
39207
|
+
const excluded = new Set([...opts.excludeRepos ?? [], ...opts.aiPolicyBlocklist ?? []].map((r) => r.toLowerCase()));
|
|
39208
|
+
const excludedOrgs = new Set((opts.excludeOrgs ?? []).map((o) => o.toLowerCase()));
|
|
39209
|
+
return (repoFullName) => {
|
|
39210
|
+
const lower = repoFullName.toLowerCase();
|
|
39211
|
+
if (excluded.has(lower))
|
|
39212
|
+
return false;
|
|
39213
|
+
const org = lower.split("/")[0];
|
|
39214
|
+
return !(org && excludedOrgs.has(org));
|
|
39215
|
+
};
|
|
39216
|
+
}
|
|
39171
39217
|
function extractLabels(item) {
|
|
39172
39218
|
if (!Array.isArray(item.labels))
|
|
39173
39219
|
return [];
|
|
@@ -39213,7 +39259,8 @@ async function vetAndClassify(item, vetter, roadmapRefs) {
|
|
|
39213
39259
|
return { ...candidate, horizon };
|
|
39214
39260
|
}
|
|
39215
39261
|
async function discoverFeatures(opts) {
|
|
39216
|
-
const
|
|
39262
|
+
const includeRepo = buildRepoExclusionFilter(opts);
|
|
39263
|
+
const anchorRepos = resolveAnchorRepos(opts.repoScores, opts.anchorThreshold).filter(includeRepo);
|
|
39217
39264
|
if (anchorRepos.length === 0) {
|
|
39218
39265
|
return {
|
|
39219
39266
|
quickWins: [],
|
|
@@ -39320,7 +39367,11 @@ async function discoverFeaturesBroad(opts) {
|
|
|
39320
39367
|
merged.push(raw);
|
|
39321
39368
|
}
|
|
39322
39369
|
}
|
|
39323
|
-
|
|
39370
|
+
const includeRepo = buildRepoExclusionFilter(opts);
|
|
39371
|
+
items = merged.filter((it) => !it.pull_request && !it.assignee && isFeatureIssue(it)).filter((it) => {
|
|
39372
|
+
const repo = extractRepoFromUrl(it.html_url);
|
|
39373
|
+
return repo === null || includeRepo(repo);
|
|
39374
|
+
}).slice(0, maxToVet);
|
|
39324
39375
|
} catch (err) {
|
|
39325
39376
|
rethrowIfFatal(err);
|
|
39326
39377
|
warn(MODULE13, `broad feature search failed: ${errorMessage(err)}`);
|
|
@@ -39654,6 +39705,13 @@ function mergeStates(local, remote) {
|
|
|
39654
39705
|
const localPrefsWin = localPrefsTs !== void 0 && (remotePrefsTs === void 0 || localPrefsTs > remotePrefsTs);
|
|
39655
39706
|
const preferences = localPrefsWin ? local.preferences : remote.preferences;
|
|
39656
39707
|
const preferencesUpdatedAt = pickFresherTimestamp(localPrefsTs, remotePrefsTs);
|
|
39708
|
+
const mergedPRsFinal = unionByUrl(local.mergedPRs, remote.mergedPRs);
|
|
39709
|
+
const closedPRsFinal = unionByUrl(local.closedPRs, remote.closedPRs);
|
|
39710
|
+
const resolvedUrls = /* @__PURE__ */ new Set([
|
|
39711
|
+
...mergedPRsFinal.map((p) => p.url),
|
|
39712
|
+
...closedPRsFinal.map((p) => p.url)
|
|
39713
|
+
]);
|
|
39714
|
+
const openPRsFinal = unionByUrl(local.openPRs ?? [], remote.openPRs ?? []).filter((p) => !resolvedUrls.has(p.url));
|
|
39657
39715
|
return {
|
|
39658
39716
|
...local,
|
|
39659
39717
|
...remote,
|
|
@@ -39664,9 +39722,9 @@ function mergeStates(local, remote) {
|
|
|
39664
39722
|
repoScores: mergeRepoScores(local.repoScores, remote.repoScores),
|
|
39665
39723
|
starredRepos: mergeStarredRepos(local, remote),
|
|
39666
39724
|
starredReposLastFetched: pickFresherTimestamp(local.starredReposLastFetched, remote.starredReposLastFetched),
|
|
39667
|
-
mergedPRs:
|
|
39668
|
-
closedPRs:
|
|
39669
|
-
openPRs:
|
|
39725
|
+
mergedPRs: mergedPRsFinal,
|
|
39726
|
+
closedPRs: closedPRsFinal,
|
|
39727
|
+
openPRs: openPRsFinal,
|
|
39670
39728
|
savedResults: savedResultsFinal,
|
|
39671
39729
|
skippedIssues,
|
|
39672
39730
|
lastSearchAt: pickFresherTimestamp(local.lastSearchAt, remote.lastSearchAt),
|
|
@@ -40038,12 +40096,16 @@ var OssScout = class {
|
|
|
40038
40096
|
languages: this.state.preferences.languages,
|
|
40039
40097
|
excludeRepos: this.state.preferences.excludeRepos,
|
|
40040
40098
|
excludeOrgs: this.state.preferences.excludeOrgs,
|
|
40099
|
+
aiPolicyBlocklist: this.state.preferences.aiPolicyBlocklist,
|
|
40041
40100
|
splitRatio: options?.splitRatio ?? this.state.preferences.featuresSplitRatio
|
|
40042
40101
|
}) : await discoverFeatures({
|
|
40043
40102
|
octokit,
|
|
40044
40103
|
vetter,
|
|
40045
40104
|
repoScores: this.state.repoScores ?? {},
|
|
40046
40105
|
count,
|
|
40106
|
+
excludeRepos: this.state.preferences.excludeRepos,
|
|
40107
|
+
excludeOrgs: this.state.preferences.excludeOrgs,
|
|
40108
|
+
aiPolicyBlocklist: this.state.preferences.aiPolicyBlocklist,
|
|
40047
40109
|
anchorThreshold: options?.anchorThreshold ?? this.state.preferences.featuresAnchorThreshold,
|
|
40048
40110
|
splitRatio: options?.splitRatio ?? this.state.preferences.featuresSplitRatio
|
|
40049
40111
|
});
|
|
@@ -40746,7 +40808,7 @@ function registerTools(server, scout) {
|
|
|
40746
40808
|
"search",
|
|
40747
40809
|
"Search for open source issues matching your preferences",
|
|
40748
40810
|
{
|
|
40749
|
-
maxResults: external_exports3.number().int().min(1).max(
|
|
40811
|
+
maxResults: external_exports3.number().int().min(1).max(100).optional().describe("Maximum number of results to return (1-100, default 10)"),
|
|
40750
40812
|
strategies: external_exports3.string().optional().describe(
|
|
40751
40813
|
"Comma-separated search strategies: merged, starred, broad, maintained, all"
|
|
40752
40814
|
),
|
|
@@ -41095,7 +41157,7 @@ function registerResources(server, scout) {
|
|
|
41095
41157
|
// src/index.ts
|
|
41096
41158
|
var SERVER_INFO = {
|
|
41097
41159
|
name: "oss-scout-mcp",
|
|
41098
|
-
version: "0.10.
|
|
41160
|
+
version: "0.10.2"
|
|
41099
41161
|
// x-release-please-version
|
|
41100
41162
|
};
|
|
41101
41163
|
function createServer(scout) {
|
package/dist/tools.js
CHANGED
|
@@ -35,9 +35,12 @@ export function registerTools(server, scout) {
|
|
|
35
35
|
.number()
|
|
36
36
|
.int()
|
|
37
37
|
.min(1)
|
|
38
|
-
|
|
38
|
+
// 100, not 50: the CLI's search count is unbounded, so a tight MCP
|
|
39
|
+
// cap made the two surfaces disagree for 1.5.0 clients while still
|
|
40
|
+
// needing SOME ceiling against absurd values (#317).
|
|
41
|
+
.max(100)
|
|
39
42
|
.optional()
|
|
40
|
-
.describe("Maximum number of results to return (1-
|
|
43
|
+
.describe("Maximum number of results to return (1-100, default 10)"),
|
|
41
44
|
strategies: z
|
|
42
45
|
.string()
|
|
43
46
|
.optional()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-scout/mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "MCP server for oss-scout — exposes issue discovery and vetting as MCP tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
20
20
|
"zod": "^4.3.6",
|
|
21
|
-
"@oss-scout/core": "^1.5.
|
|
21
|
+
"@oss-scout/core": "^1.5.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^25.5.0",
|