@oss-scout/mcp 0.6.0 → 0.8.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/dist/mcp-server.bundle.cjs +126 -9
- package/dist/tools.js +18 -1
- package/package.json +2 -2
|
@@ -37542,6 +37542,32 @@ async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery,
|
|
|
37542
37542
|
}
|
|
37543
37543
|
return allItems;
|
|
37544
37544
|
}
|
|
37545
|
+
function buildLanguageVariants(languages, isAnyLanguage, hasLabels) {
|
|
37546
|
+
if (isAnyLanguage || languages.length === 0)
|
|
37547
|
+
return [""];
|
|
37548
|
+
if (languages.length === 1)
|
|
37549
|
+
return [`language:${languages[0]}`];
|
|
37550
|
+
if (!hasLabels)
|
|
37551
|
+
return [languages.map((l) => `language:${l}`).join(" ")];
|
|
37552
|
+
return languages.map((l) => `language:${l}`);
|
|
37553
|
+
}
|
|
37554
|
+
async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, buildBaseQuery, perPage) {
|
|
37555
|
+
const langVariants = buildLanguageVariants(languages, isAnyLanguage, labels.length > 0);
|
|
37556
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
37557
|
+
const allItems = [];
|
|
37558
|
+
for (let i = 0; i < langVariants.length; i++) {
|
|
37559
|
+
if (i > 0)
|
|
37560
|
+
await sleep(INTER_QUERY_DELAY_MS);
|
|
37561
|
+
const items = await searchWithChunkedLabels(octokit, labels, 0, (labelQ) => `${buildBaseQuery(langVariants[i])} ${labelQ}`.replace(/ +/g, " ").trim(), perPage);
|
|
37562
|
+
for (const item of items) {
|
|
37563
|
+
if (!seenUrls.has(item.html_url)) {
|
|
37564
|
+
seenUrls.add(item.html_url);
|
|
37565
|
+
allItems.push(item);
|
|
37566
|
+
}
|
|
37567
|
+
}
|
|
37568
|
+
}
|
|
37569
|
+
return allItems;
|
|
37570
|
+
}
|
|
37545
37571
|
async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets, remainingNeeded, minStars, phaseLabel) {
|
|
37546
37572
|
const spamRepos = detectLabelFarmingRepos(items);
|
|
37547
37573
|
if (spamRepos.size > 0) {
|
|
@@ -37574,6 +37600,72 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37574
37600
|
return { candidates: starFiltered, allVetFailed, rateLimitHit };
|
|
37575
37601
|
}
|
|
37576
37602
|
|
|
37603
|
+
// ../core/dist/core/personalization.js
|
|
37604
|
+
var REPO_BOOST = 20;
|
|
37605
|
+
var LANGUAGE_BOOST = 10;
|
|
37606
|
+
function annotateBoost(candidates, preferLanguages, preferRepos) {
|
|
37607
|
+
const langSet = new Set((preferLanguages ?? []).map((l) => l.trim().toLowerCase()).filter(Boolean));
|
|
37608
|
+
const repoSet = new Set((preferRepos ?? []).map((r) => r.trim()).filter(Boolean));
|
|
37609
|
+
if (langSet.size === 0 && repoSet.size === 0)
|
|
37610
|
+
return;
|
|
37611
|
+
for (const c of candidates) {
|
|
37612
|
+
let score = 0;
|
|
37613
|
+
const reasons = [];
|
|
37614
|
+
if (repoSet.size > 0 && repoSet.has(c.issue.repo)) {
|
|
37615
|
+
score += REPO_BOOST;
|
|
37616
|
+
reasons.push(`repo affinity: ${c.issue.repo}`);
|
|
37617
|
+
}
|
|
37618
|
+
const lang = c.projectHealth.language;
|
|
37619
|
+
if (langSet.size > 0 && lang && langSet.has(lang.toLowerCase())) {
|
|
37620
|
+
score += LANGUAGE_BOOST;
|
|
37621
|
+
reasons.push(`language match: ${lang}`);
|
|
37622
|
+
}
|
|
37623
|
+
if (score > 0) {
|
|
37624
|
+
c.boostScore = score;
|
|
37625
|
+
c.boostReasons = reasons;
|
|
37626
|
+
}
|
|
37627
|
+
}
|
|
37628
|
+
}
|
|
37629
|
+
function applyDiversityRatio(candidates, maxResults, diversityRatio) {
|
|
37630
|
+
if (maxResults <= 0)
|
|
37631
|
+
return [];
|
|
37632
|
+
const ratio = Math.max(0, Math.min(1, diversityRatio));
|
|
37633
|
+
if (ratio === 0)
|
|
37634
|
+
return candidates.slice(0, maxResults);
|
|
37635
|
+
const diversityReserve = Math.min(Math.floor(maxResults * ratio), maxResults);
|
|
37636
|
+
if (diversityReserve === 0)
|
|
37637
|
+
return candidates.slice(0, maxResults);
|
|
37638
|
+
const mainBudget = maxResults - diversityReserve;
|
|
37639
|
+
const picks = [];
|
|
37640
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37641
|
+
for (const c of candidates) {
|
|
37642
|
+
if (picks.length >= mainBudget)
|
|
37643
|
+
break;
|
|
37644
|
+
picks.push(c);
|
|
37645
|
+
seen.add(c.issue.url);
|
|
37646
|
+
}
|
|
37647
|
+
for (const c of candidates) {
|
|
37648
|
+
if (picks.length >= maxResults)
|
|
37649
|
+
break;
|
|
37650
|
+
if (seen.has(c.issue.url))
|
|
37651
|
+
continue;
|
|
37652
|
+
if (c.boostScore && c.boostScore > 0)
|
|
37653
|
+
continue;
|
|
37654
|
+
c.diversitySlot = true;
|
|
37655
|
+
picks.push(c);
|
|
37656
|
+
seen.add(c.issue.url);
|
|
37657
|
+
}
|
|
37658
|
+
for (const c of candidates) {
|
|
37659
|
+
if (picks.length >= maxResults)
|
|
37660
|
+
break;
|
|
37661
|
+
if (seen.has(c.issue.url))
|
|
37662
|
+
continue;
|
|
37663
|
+
picks.push(c);
|
|
37664
|
+
seen.add(c.issue.url);
|
|
37665
|
+
}
|
|
37666
|
+
return picks;
|
|
37667
|
+
}
|
|
37668
|
+
|
|
37577
37669
|
// ../core/dist/core/issue-discovery.js
|
|
37578
37670
|
var MODULE10 = "issue-discovery";
|
|
37579
37671
|
var LOW_BUDGET_THRESHOLD = 20;
|
|
@@ -37629,7 +37721,7 @@ async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssue
|
|
|
37629
37721
|
rateLimitHit
|
|
37630
37722
|
};
|
|
37631
37723
|
}
|
|
37632
|
-
async function runPhase2(octokit, vetter, scopes, labels, configLabels,
|
|
37724
|
+
async function runPhase2(octokit, vetter, scopes, labels, configLabels, languages, isAnyLanguage, maxResults, minStars, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues) {
|
|
37633
37725
|
info(MODULE10, "Phase 2: General issue search...");
|
|
37634
37726
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
37635
37727
|
const tierLabelGroups = [];
|
|
@@ -37656,7 +37748,7 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, baseQual
|
|
|
37656
37748
|
let rateLimitHit = false;
|
|
37657
37749
|
for (const { tier, tierLabels } of tierLabelGroups) {
|
|
37658
37750
|
try {
|
|
37659
|
-
const allItems = await
|
|
37751
|
+
const allItems = await searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, tierLabels, (langQ) => `is:issue is:open ${langQ} no:assignee`.replace(/ +/g, " ").trim(), budgetPerTier * 3);
|
|
37660
37752
|
info(MODULE10, `Phase 2 [${tier}]: processing ${allItems.length} items...`);
|
|
37661
37753
|
const { candidates: tierCandidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], budgetPerTier, minStars, `Phase 2 [${tier}]`);
|
|
37662
37754
|
tierResults.push(tierCandidates);
|
|
@@ -37850,7 +37942,6 @@ var IssueDiscovery = class {
|
|
|
37850
37942
|
const lowScoringRepos = new Set(this.deriveLowScoringRepos(config2.minRepoScoreThreshold));
|
|
37851
37943
|
const isAnyLanguage = languages.some((l) => l.toLowerCase() === "any");
|
|
37852
37944
|
const langQuery = isAnyLanguage ? "" : languages.map((l) => `language:${l}`).join(" ");
|
|
37853
|
-
const baseQualifiers = `is:issue is:open ${langQuery} no:assignee`.replace(/ +/g, " ").trim();
|
|
37854
37945
|
const aiBlocklisted = new Set(config2.aiPolicyBlocklist);
|
|
37855
37946
|
if (aiBlocklisted.size > 0) {
|
|
37856
37947
|
debug(MODULE10, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
|
|
@@ -37922,7 +38013,7 @@ var IssueDiscovery = class {
|
|
|
37922
38013
|
info(MODULE10, `Skipping broad phase delay: no results from previous phases, proceeding immediately`);
|
|
37923
38014
|
}
|
|
37924
38015
|
const remaining = maxResults - allCandidates.length;
|
|
37925
|
-
const result = await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels,
|
|
38016
|
+
const result = await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels, languages, isAnyLanguage, remaining, minStars, phase0RepoSet, starredRepoSet, allCandidates, filterIssues);
|
|
37926
38017
|
allCandidates.push(...result.candidates);
|
|
37927
38018
|
phaseErrors["2"] = result.error;
|
|
37928
38019
|
if (result.rateLimitHit)
|
|
@@ -37967,6 +38058,7 @@ var IssueDiscovery = class {
|
|
|
37967
38058
|
if (rateLimitHitDuringSearch || phasesSkippedForBudget) {
|
|
37968
38059
|
this.rateLimitWarning = `Search results may be incomplete: GitHub API rate limits were hit during search.${budgetNote} Found ${allCandidates.length} candidate${allCandidates.length === 1 ? "" : "s"} but some search phases were limited. Try again after the rate limit resets for complete results.`;
|
|
37969
38060
|
}
|
|
38061
|
+
annotateBoost(allCandidates, options.preferLanguages, options.preferRepos);
|
|
37970
38062
|
allCandidates.sort((a, b) => {
|
|
37971
38063
|
const priorityOrder = {
|
|
37972
38064
|
merged_pr: 0,
|
|
@@ -37980,11 +38072,15 @@ var IssueDiscovery = class {
|
|
|
37980
38072
|
const recDiff = recommendationOrder[a.recommendation] - recommendationOrder[b.recommendation];
|
|
37981
38073
|
if (recDiff !== 0)
|
|
37982
38074
|
return recDiff;
|
|
38075
|
+
const boostDiff = (b.boostScore ?? 0) - (a.boostScore ?? 0);
|
|
38076
|
+
if (boostDiff !== 0)
|
|
38077
|
+
return boostDiff;
|
|
37983
38078
|
return b.viabilityScore - a.viabilityScore;
|
|
37984
38079
|
});
|
|
37985
38080
|
const capped = applyPerRepoCap(allCandidates, 2);
|
|
37986
|
-
|
|
37987
|
-
|
|
38081
|
+
const finalPicks = applyDiversityRatio(capped, maxResults, options.diversityRatio ?? 0);
|
|
38082
|
+
info(MODULE10, `Search complete: ${tracker.getTotalCalls()} Search API calls used, ${finalPicks.length} candidates returned`);
|
|
38083
|
+
return { candidates: finalPicks, strategiesUsed };
|
|
37988
38084
|
}
|
|
37989
38085
|
/**
|
|
37990
38086
|
* Vet a specific issue for claimability and project health.
|
|
@@ -38812,7 +38908,10 @@ var OssScout = class {
|
|
|
38812
38908
|
const { candidates, strategiesUsed } = await discovery.searchIssues({
|
|
38813
38909
|
maxResults: options?.maxResults,
|
|
38814
38910
|
strategies: options?.strategies,
|
|
38815
|
-
skippedUrls
|
|
38911
|
+
skippedUrls,
|
|
38912
|
+
preferLanguages: options?.preferLanguages,
|
|
38913
|
+
preferRepos: options?.preferRepos,
|
|
38914
|
+
diversityRatio: options?.diversityRatio
|
|
38816
38915
|
});
|
|
38817
38916
|
this.state.lastSearchAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38818
38917
|
this.dirty = true;
|
|
@@ -39271,15 +39370,33 @@ function registerTools(server, scout) {
|
|
|
39271
39370
|
maxResults: external_exports3.number().optional().describe("Maximum number of results to return (default 10)"),
|
|
39272
39371
|
strategies: external_exports3.string().optional().describe(
|
|
39273
39372
|
"Comma-separated search strategies: merged, starred, broad, maintained, all"
|
|
39373
|
+
),
|
|
39374
|
+
preferLanguages: external_exports3.array(external_exports3.string()).optional().describe(
|
|
39375
|
+
"Soft-boost ranking for candidates whose repo language matches one of these (case-insensitive). Personalization tier between recommendation and viabilityScore (#1244). Not a filter; only reorders."
|
|
39376
|
+
),
|
|
39377
|
+
preferRepos: external_exports3.array(external_exports3.string()).optional().describe(
|
|
39378
|
+
"Soft-boost ranking for candidates in one of these `owner/repo` slugs. Stronger weight than language match (#1244). Not a filter; only reorders."
|
|
39379
|
+
),
|
|
39380
|
+
diversityRatio: external_exports3.number().min(0).max(1).optional().describe(
|
|
39381
|
+
"Diversity counterweight (#1244): fraction of result slots reserved for candidates that matched NEITHER preference list. Counters echo-chamber bias when boosts accumulate over time. 0 disables, 1 reserves every slot for diversity. Default 0."
|
|
39274
39382
|
)
|
|
39275
39383
|
},
|
|
39276
|
-
async ({
|
|
39384
|
+
async ({
|
|
39385
|
+
maxResults,
|
|
39386
|
+
strategies,
|
|
39387
|
+
preferLanguages,
|
|
39388
|
+
preferRepos,
|
|
39389
|
+
diversityRatio
|
|
39390
|
+
}) => {
|
|
39277
39391
|
try {
|
|
39278
39392
|
const parsedStrategies = strategies ? strategies.split(",").map((s) => SearchStrategySchema.parse(s.trim())) : void 0;
|
|
39279
39393
|
const result = await withTimeout(
|
|
39280
39394
|
scout.search({
|
|
39281
39395
|
maxResults: maxResults ?? 10,
|
|
39282
|
-
strategies: parsedStrategies
|
|
39396
|
+
strategies: parsedStrategies,
|
|
39397
|
+
preferLanguages,
|
|
39398
|
+
preferRepos,
|
|
39399
|
+
diversityRatio
|
|
39283
39400
|
})
|
|
39284
39401
|
);
|
|
39285
39402
|
scout.saveResults(result.candidates);
|
package/dist/tools.js
CHANGED
|
@@ -18,7 +18,21 @@ export function registerTools(server, scout) {
|
|
|
18
18
|
.string()
|
|
19
19
|
.optional()
|
|
20
20
|
.describe("Comma-separated search strategies: merged, starred, broad, maintained, all"),
|
|
21
|
-
|
|
21
|
+
preferLanguages: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Soft-boost ranking for candidates whose repo language matches one of these (case-insensitive). Personalization tier between recommendation and viabilityScore (#1244). Not a filter; only reorders."),
|
|
25
|
+
preferRepos: z
|
|
26
|
+
.array(z.string())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Soft-boost ranking for candidates in one of these `owner/repo` slugs. Stronger weight than language match (#1244). Not a filter; only reorders."),
|
|
29
|
+
diversityRatio: z
|
|
30
|
+
.number()
|
|
31
|
+
.min(0)
|
|
32
|
+
.max(1)
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Diversity counterweight (#1244): fraction of result slots reserved for candidates that matched NEITHER preference list. Counters echo-chamber bias when boosts accumulate over time. 0 disables, 1 reserves every slot for diversity. Default 0."),
|
|
35
|
+
}, async ({ maxResults, strategies, preferLanguages, preferRepos, diversityRatio, }) => {
|
|
22
36
|
try {
|
|
23
37
|
const parsedStrategies = strategies
|
|
24
38
|
? strategies
|
|
@@ -28,6 +42,9 @@ export function registerTools(server, scout) {
|
|
|
28
42
|
const result = await withTimeout(scout.search({
|
|
29
43
|
maxResults: maxResults ?? 10,
|
|
30
44
|
strategies: parsedStrategies,
|
|
45
|
+
preferLanguages,
|
|
46
|
+
preferRepos,
|
|
47
|
+
diversityRatio,
|
|
31
48
|
}));
|
|
32
49
|
scout.saveResults(result.candidates);
|
|
33
50
|
await scout.checkpoint();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-scout/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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": "^0.
|
|
21
|
+
"@oss-scout/core": "^0.11.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^25.5.0",
|