@oss-scout/mcp 0.10.1 → 0.11.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/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.1";
15
+ readonly version: "0.11.0";
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.1", // x-release-please-version
18
+ version: "0.11.0", // x-release-please-version
19
19
  };
20
20
  /**
21
21
  * Build a fully-wired MCP server (tools + resources) around a scout.
@@ -36075,6 +36075,7 @@ var IssueScopeSchema = external_exports3.enum([
36075
36075
  ]);
36076
36076
  var SearchStrategySchema = external_exports3.enum([
36077
36077
  "merged",
36078
+ "orgs",
36078
36079
  "starred",
36079
36080
  "broad",
36080
36081
  "maintained",
@@ -36082,6 +36083,7 @@ var SearchStrategySchema = external_exports3.enum([
36082
36083
  ]);
36083
36084
  var CONCRETE_STRATEGIES = [
36084
36085
  "merged",
36086
+ "orgs",
36085
36087
  "starred",
36086
36088
  "broad",
36087
36089
  "maintained"
@@ -36215,6 +36217,9 @@ var ScoutPreferencesSchema = external_exports3.looseObject({
36215
36217
  scope: external_exports3.array(IssueScopeSchema).optional(),
36216
36218
  excludeRepos: external_exports3.array(external_exports3.string()).default([]),
36217
36219
  excludeOrgs: external_exports3.array(external_exports3.string()).default([]),
36220
+ // Orgs whose repos the "orgs" search phase targets with org: qualifiers.
36221
+ // Empty (the default) disables the phase even when the strategy is enabled.
36222
+ preferredOrgs: external_exports3.array(external_exports3.string()).default([]),
36218
36223
  aiPolicyBlocklist: external_exports3.array(external_exports3.string()).default(["matplotlib/matplotlib"]),
36219
36224
  projectCategories: external_exports3.array(ProjectCategorySchema).default([]),
36220
36225
  minStars: external_exports3.number().default(50),
@@ -37684,7 +37689,11 @@ async function triageWithSLM(input, options) {
37684
37689
  format: SLM_TRIAGE_SCHEMA,
37685
37690
  options: { temperature: 0.1 }
37686
37691
  }),
37687
- signal: AbortSignal.timeout(timeoutMs)
37692
+ signal: AbortSignal.timeout(timeoutMs),
37693
+ // No redirect following: an allowed private host could otherwise
37694
+ // 307/308-redirect the issue-text POST body to a public origin (#310).
37695
+ // Ollama never redirects, so this costs nothing.
37696
+ redirect: "error"
37688
37697
  });
37689
37698
  } catch {
37690
37699
  return null;
@@ -38079,6 +38088,9 @@ var IssueVetter = class {
38079
38088
  let rateLimitFailures = 0;
38080
38089
  let attemptedCount = 0;
38081
38090
  let firstAuthError = null;
38091
+ let firstForbiddenError = null;
38092
+ let forbiddenCount = 0;
38093
+ const forbiddenRepos = /* @__PURE__ */ new Set();
38082
38094
  const uniqueUrls = [...new Set(urls)];
38083
38095
  const issueRefs = uniqueUrls.flatMap((url2) => {
38084
38096
  const p = parseGitHubUrl(url2);
@@ -38097,6 +38109,12 @@ var IssueVetter = class {
38097
38109
  break;
38098
38110
  if (firstAuthError)
38099
38111
  break;
38112
+ const parsedUrl = parseGitHubUrl(url2);
38113
+ const repoKey = parsedUrl ? `${parsedUrl.owner}/${parsedUrl.repo}` : null;
38114
+ if (repoKey && forbiddenRepos.has(repoKey)) {
38115
+ debug(MODULE10, `Skipping ${url2}: repo already returned 403`);
38116
+ continue;
38117
+ }
38100
38118
  attemptedCount++;
38101
38119
  const core = prefetchFor(url2);
38102
38120
  const task = this.vetIssue(url2, core ? { prefetched: core } : void 0).then((candidate) => {
@@ -38107,10 +38125,19 @@ var IssueVetter = class {
38107
38125
  candidates.push(candidate);
38108
38126
  }
38109
38127
  }).catch((error48) => {
38110
- if (isAuthError(error48)) {
38128
+ if (getHttpStatusCode(error48) === 401) {
38111
38129
  firstAuthError ??= error48;
38112
38130
  return;
38113
38131
  }
38132
+ if (isAuthError(error48)) {
38133
+ firstForbiddenError ??= error48;
38134
+ forbiddenCount++;
38135
+ failedVettingCount++;
38136
+ if (repoKey)
38137
+ forbiddenRepos.add(repoKey);
38138
+ warn(MODULE10, `Access forbidden for ${url2} (SAML/token scope?) \u2014 skipping this repo:`, errorMessage(error48));
38139
+ return;
38140
+ }
38114
38141
  failedVettingCount++;
38115
38142
  if (isRateLimitError(error48)) {
38116
38143
  rateLimitFailures++;
@@ -38129,6 +38156,9 @@ var IssueVetter = class {
38129
38156
  }
38130
38157
  throw firstAuthError;
38131
38158
  }
38159
+ if (firstForbiddenError && attemptedCount > 0 && forbiddenCount === attemptedCount) {
38160
+ throw firstForbiddenError;
38161
+ }
38132
38162
  const allFailed = failedVettingCount === attemptedCount && attemptedCount > 0;
38133
38163
  if (allFailed) {
38134
38164
  warn(MODULE10, `All ${attemptedCount} issue(s) failed vetting. This may indicate a systemic issue (rate limit, auth, network).`);
@@ -38395,7 +38425,7 @@ function buildLanguageVariants(languages, isAnyLanguage, hasLabels) {
38395
38425
  return [languages.map((l) => `language:${l}`).join(" ")];
38396
38426
  return languages.map((l) => `language:${l}`);
38397
38427
  }
38398
- async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, buildBaseQuery, perPage, tracker = getSearchBudgetTracker(), startOffset = 0) {
38428
+ async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, buildBaseQuery, perPage, tracker = getSearchBudgetTracker(), startOffset = 0, reservedOps = 0) {
38399
38429
  const variants = buildLanguageVariants(languages, isAnyLanguage, labels.length > 0);
38400
38430
  const k = variants.length > 0 ? startOffset % variants.length : 0;
38401
38431
  const langVariants = [...variants.slice(k), ...variants.slice(0, k)];
@@ -38404,7 +38434,7 @@ async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage,
38404
38434
  for (let i = 0; i < langVariants.length; i++) {
38405
38435
  if (i > 0)
38406
38436
  await sleep(GRAPHQL_INTER_QUERY_DELAY_MS);
38407
- const items = await searchWithChunkedLabels(octokit, labels, 0, (labelQ) => `${buildBaseQuery(langVariants[i])} ${labelQ}`.replace(/ +/g, " ").trim(), perPage, tracker);
38437
+ const items = await searchWithChunkedLabels(octokit, labels, reservedOps, (labelQ) => `${buildBaseQuery(langVariants[i])} ${labelQ}`.replace(/ +/g, " ").trim(), perPage, tracker);
38408
38438
  for (const item of items) {
38409
38439
  if (!seenUrls.has(item.html_url)) {
38410
38440
  seenUrls.add(item.html_url);
@@ -38582,6 +38612,35 @@ async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
38582
38612
  rateLimitHit
38583
38613
  };
38584
38614
  }
38615
+ var MAX_QUERY_ORGS = 8;
38616
+ async function runPhaseOrgs(octokit, vetter, orgs, languages, isAnyLanguage, labels, maxResults, minStars, phase0RepoSet, starredRepoSet, filterIssues, tracker) {
38617
+ info(MODULE12, `Orgs phase: searching ${orgs.length} preferred org(s)...`);
38618
+ const queryOrgs = orgs.slice(0, MAX_QUERY_ORGS);
38619
+ if (orgs.length > MAX_QUERY_ORGS) {
38620
+ warn(MODULE12, `Orgs phase: capping query at ${MAX_QUERY_ORGS} org(s); dropping ${orgs.slice(MAX_QUERY_ORGS).join(", ")}`);
38621
+ }
38622
+ const orgQuery = queryOrgs.map((o) => `org:${o}`).join(" ");
38623
+ try {
38624
+ const allItems = await searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, (langQ) => `is:issue is:open ${langQ} no:assignee ${orgQuery}`.replace(/ +/g, " ").trim(), maxResults * 3, tracker, 0, queryOrgs.length);
38625
+ const { candidates, allVetFailed, rateLimitHit } = await filterVetAndScore(vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet], maxResults, minStars, "Orgs phase");
38626
+ info(MODULE12, `Found ${candidates.length} candidates from preferred orgs`);
38627
+ return {
38628
+ candidates,
38629
+ error: allVetFailed ? "all vetting failed" : null,
38630
+ rateLimitHit
38631
+ };
38632
+ } catch (error48) {
38633
+ if (getHttpStatusCode(error48) === 401)
38634
+ throw error48;
38635
+ const errMsg = errorMessage(error48);
38636
+ warn(MODULE12, `Error in preferred-orgs search: ${errMsg}`);
38637
+ return {
38638
+ candidates: [],
38639
+ error: errMsg,
38640
+ rateLimitHit: isRateLimitError(error48)
38641
+ };
38642
+ }
38643
+ }
38585
38644
  async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssues) {
38586
38645
  info(MODULE12, `Phase 1: Searching issues in ${repos.length} starred repos...`);
38587
38646
  const phase1Labels = labels.slice(0, 3);
@@ -38806,6 +38865,7 @@ var IssueDiscovery = class {
38806
38865
  resetGraphQLSearchQueryCount();
38807
38866
  const tracker = this.budgetTracker;
38808
38867
  let searchBudget = CRITICAL_BUDGET_THRESHOLD - 1;
38868
+ let preflightFailed = false;
38809
38869
  try {
38810
38870
  const rateLimit = await checkRateLimit(this.githubToken);
38811
38871
  searchBudget = rateLimit.remaining;
@@ -38823,6 +38883,7 @@ var IssueDiscovery = class {
38823
38883
  } catch (error48) {
38824
38884
  if (getHttpStatusCode(error48) === 401)
38825
38885
  throw error48;
38886
+ preflightFailed = true;
38826
38887
  tracker.init(CRITICAL_BUDGET_THRESHOLD, new Date(Date.now() + 6e4).toISOString());
38827
38888
  warn(MODULE12, "Could not check rate limit \u2014 using conservative budget, skipping the starred phase:", errorMessage(error48));
38828
38889
  }
@@ -38869,7 +38930,9 @@ var IssueDiscovery = class {
38869
38930
  break;
38870
38931
  }
38871
38932
  const phase0RepoSet = new Set(phase0Repos);
38872
- const otherStrategiesCanRun = starredRepos.length > 0 && enabledStrategies.has("starred") || enabledStrategies.has("broad") || enabledStrategies.has("maintained");
38933
+ const preferredOrgs = config2.preferredOrgs ?? [];
38934
+ const orgsPhaseEnabled = preferredOrgs.length > 0 && enabledStrategies.has("orgs");
38935
+ const otherStrategiesCanRun = orgsPhaseEnabled || starredRepos.length > 0 && enabledStrategies.has("starred") || enabledStrategies.has("broad") || enabledStrategies.has("maintained");
38873
38936
  if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
38874
38937
  const phase0Cap = otherStrategiesCanRun ? Math.max(1, Math.ceil(maxResults * PHASE0_MAX_SHARE)) : maxResults;
38875
38938
  const remaining = Math.min(maxResults - allCandidates.length, phase0Cap);
@@ -38879,6 +38942,13 @@ var IssueDiscovery = class {
38879
38942
  }
38880
38943
  strategiesUsed.push("merged");
38881
38944
  }
38945
+ if (allCandidates.length < maxResults && orgsPhaseEnabled) {
38946
+ await applyInterPhaseDelay();
38947
+ const remaining = maxResults - allCandidates.length;
38948
+ const result = await runPhaseOrgs(this.octokit, this.vetter, preferredOrgs, languages, isAnyLanguage, labels, remaining, minStars, phase0RepoSet, starredRepoSet, filterIssues, tracker);
38949
+ recordPhaseResult("orgs", result);
38950
+ strategiesUsed.push("orgs");
38951
+ }
38882
38952
  if (allCandidates.length < maxResults && starredRepos.length > 0 && searchBudget >= CRITICAL_BUDGET_THRESHOLD && enabledStrategies.has("starred")) {
38883
38953
  await applyInterPhaseDelay();
38884
38954
  const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
@@ -38898,7 +38968,8 @@ var IssueDiscovery = class {
38898
38968
  if (viableBeforeBroad >= maxResults && enabledStrategies.has("broad")) {
38899
38969
  info(MODULE12, `Skipping broad search: ${viableBeforeBroad} viable candidate(s) already meet maxResults (${maxResults})`);
38900
38970
  } else if (enabledStrategies.has("broad")) {
38901
- const newRepoCandidateCount = allCandidates.filter((c) => c.recommendation !== "skip" && !phase0RepoSet.has(c.issue.repo) && !starredRepoSet.has(c.issue.repo)).length;
38971
+ const preferredOrgSet = new Set(preferredOrgs.map((o) => o.toLowerCase()));
38972
+ const newRepoCandidateCount = allCandidates.filter((c) => c.recommendation !== "skip" && !phase0RepoSet.has(c.issue.repo) && !starredRepoSet.has(c.issue.repo) && !preferredOrgSet.has(c.issue.repo.split("/")[0]?.toLowerCase() ?? "")).length;
38902
38973
  if (skipThreshold > 0 && newRepoCandidateCount >= skipThreshold) {
38903
38974
  info(MODULE12, `Skipping broad search: already found ${newRepoCandidateCount} viable candidate(s) from new repos (threshold: ${skipThreshold})`);
38904
38975
  } else {
@@ -38926,10 +38997,11 @@ var IssueDiscovery = class {
38926
38997
  strategiesUsed.push("maintained");
38927
38998
  }
38928
38999
  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.` : "";
39000
+ 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
39001
  if (allCandidates.length === 0) {
38931
39002
  const errorDetails = [
38932
39003
  phaseErrors["0"] ? `Phase 0 (merged-PR repos): ${phaseErrors["0"]}` : null,
39004
+ phaseErrors["orgs"] ? `Orgs phase (preferred orgs): ${phaseErrors["orgs"]}` : null,
38933
39005
  phaseErrors["1"] ? `Phase 1 (starred repos): ${phaseErrors["1"]}` : null,
38934
39006
  phaseErrors["2"] ? `Phase 2 (general): ${phaseErrors["2"]}` : null,
38935
39007
  phaseErrors["3"] ? `Phase 3 (maintained repos): ${phaseErrors["3"]}` : null
@@ -39034,10 +39106,17 @@ function parseRoadmapIssueRefs(content, owner, repo) {
39034
39106
  refs.add(n);
39035
39107
  }
39036
39108
  const fullRefStripPattern = /\b[\w.-]+\/[\w.-]+#\d+\b/gi;
39109
+ let inFence = false;
39037
39110
  for (const line of content.split("\n")) {
39111
+ if (/^\s*(```|~~~)/.test(line)) {
39112
+ inFence = !inFence;
39113
+ continue;
39114
+ }
39115
+ if (inFence)
39116
+ continue;
39038
39117
  if (/^\s*#+\s/.test(line))
39039
39118
  continue;
39040
- const stripped = line.replace(fullRefStripPattern, "");
39119
+ const stripped = line.replace(fullRefStripPattern, "").replace(/`[^`]*`/g, "");
39041
39120
  for (const m of stripped.matchAll(/(?:^|[^&\w])#(\d+)\b/g)) {
39042
39121
  const n = Number.parseInt(m[1], 10);
39043
39122
  if (n > 0)
@@ -39114,7 +39193,8 @@ function classifyHorizon(input) {
39114
39193
  function splitByHorizon(candidates, count, ratio = DEFAULT_SPLIT_RATIO) {
39115
39194
  const allQuick = candidates.filter((c) => c.horizon === "quick-win").sort((a, b) => b.viabilityScore - a.viabilityScore);
39116
39195
  const allBigger = candidates.filter((c) => c.horizon === "bigger-bet").sort((a, b) => b.viabilityScore - a.viabilityScore);
39117
- const targetQuick = Math.round(count * ratio);
39196
+ const clampedRatio = Math.max(0, Math.min(1, ratio));
39197
+ const targetQuick = Math.round(count * clampedRatio);
39118
39198
  const targetBigger = count - targetQuick;
39119
39199
  const quickTaken = Math.min(allQuick.length, targetQuick);
39120
39200
  const biggerTaken = Math.min(allBigger.length, targetBigger);
@@ -39168,6 +39248,17 @@ function detectWontfixNoContributor(input) {
39168
39248
  }
39169
39249
  var NO_ANCHORS_MESSAGE = "No anchor repos yet (need 3+ merged PRs in a repo). Try `scout search` to build relationships first.";
39170
39250
  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.";
39251
+ function buildRepoExclusionFilter(opts) {
39252
+ const excluded = new Set([...opts.excludeRepos ?? [], ...opts.aiPolicyBlocklist ?? []].map((r) => r.toLowerCase()));
39253
+ const excludedOrgs = new Set((opts.excludeOrgs ?? []).map((o) => o.toLowerCase()));
39254
+ return (repoFullName) => {
39255
+ const lower = repoFullName.toLowerCase();
39256
+ if (excluded.has(lower))
39257
+ return false;
39258
+ const org = lower.split("/")[0];
39259
+ return !(org && excludedOrgs.has(org));
39260
+ };
39261
+ }
39171
39262
  function extractLabels(item) {
39172
39263
  if (!Array.isArray(item.labels))
39173
39264
  return [];
@@ -39213,7 +39304,8 @@ async function vetAndClassify(item, vetter, roadmapRefs) {
39213
39304
  return { ...candidate, horizon };
39214
39305
  }
39215
39306
  async function discoverFeatures(opts) {
39216
- const anchorRepos = resolveAnchorRepos(opts.repoScores, opts.anchorThreshold);
39307
+ const includeRepo = buildRepoExclusionFilter(opts);
39308
+ const anchorRepos = resolveAnchorRepos(opts.repoScores, opts.anchorThreshold).filter(includeRepo);
39217
39309
  if (anchorRepos.length === 0) {
39218
39310
  return {
39219
39311
  quickWins: [],
@@ -39320,7 +39412,11 @@ async function discoverFeaturesBroad(opts) {
39320
39412
  merged.push(raw);
39321
39413
  }
39322
39414
  }
39323
- items = merged.filter((it) => !it.pull_request && !it.assignee && isFeatureIssue(it)).slice(0, maxToVet);
39415
+ const includeRepo = buildRepoExclusionFilter(opts);
39416
+ items = merged.filter((it) => !it.pull_request && !it.assignee && isFeatureIssue(it)).filter((it) => {
39417
+ const repo = extractRepoFromUrl(it.html_url);
39418
+ return repo === null || includeRepo(repo);
39419
+ }).slice(0, maxToVet);
39324
39420
  } catch (err) {
39325
39421
  rethrowIfFatal(err);
39326
39422
  warn(MODULE13, `broad feature search failed: ${errorMessage(err)}`);
@@ -39654,6 +39750,13 @@ function mergeStates(local, remote) {
39654
39750
  const localPrefsWin = localPrefsTs !== void 0 && (remotePrefsTs === void 0 || localPrefsTs > remotePrefsTs);
39655
39751
  const preferences = localPrefsWin ? local.preferences : remote.preferences;
39656
39752
  const preferencesUpdatedAt = pickFresherTimestamp(localPrefsTs, remotePrefsTs);
39753
+ const mergedPRsFinal = unionByUrl(local.mergedPRs, remote.mergedPRs);
39754
+ const closedPRsFinal = unionByUrl(local.closedPRs, remote.closedPRs);
39755
+ const resolvedUrls = /* @__PURE__ */ new Set([
39756
+ ...mergedPRsFinal.map((p) => p.url),
39757
+ ...closedPRsFinal.map((p) => p.url)
39758
+ ]);
39759
+ const openPRsFinal = unionByUrl(local.openPRs ?? [], remote.openPRs ?? []).filter((p) => !resolvedUrls.has(p.url));
39657
39760
  return {
39658
39761
  ...local,
39659
39762
  ...remote,
@@ -39664,9 +39767,9 @@ function mergeStates(local, remote) {
39664
39767
  repoScores: mergeRepoScores(local.repoScores, remote.repoScores),
39665
39768
  starredRepos: mergeStarredRepos(local, remote),
39666
39769
  starredReposLastFetched: pickFresherTimestamp(local.starredReposLastFetched, remote.starredReposLastFetched),
39667
- mergedPRs: unionByUrl(local.mergedPRs, remote.mergedPRs),
39668
- closedPRs: unionByUrl(local.closedPRs, remote.closedPRs),
39669
- openPRs: unionByUrl(local.openPRs ?? [], remote.openPRs ?? []),
39770
+ mergedPRs: mergedPRsFinal,
39771
+ closedPRs: closedPRsFinal,
39772
+ openPRs: openPRsFinal,
39670
39773
  savedResults: savedResultsFinal,
39671
39774
  skippedIssues,
39672
39775
  lastSearchAt: pickFresherTimestamp(local.lastSearchAt, remote.lastSearchAt),
@@ -40038,12 +40141,16 @@ var OssScout = class {
40038
40141
  languages: this.state.preferences.languages,
40039
40142
  excludeRepos: this.state.preferences.excludeRepos,
40040
40143
  excludeOrgs: this.state.preferences.excludeOrgs,
40144
+ aiPolicyBlocklist: this.state.preferences.aiPolicyBlocklist,
40041
40145
  splitRatio: options?.splitRatio ?? this.state.preferences.featuresSplitRatio
40042
40146
  }) : await discoverFeatures({
40043
40147
  octokit,
40044
40148
  vetter,
40045
40149
  repoScores: this.state.repoScores ?? {},
40046
40150
  count,
40151
+ excludeRepos: this.state.preferences.excludeRepos,
40152
+ excludeOrgs: this.state.preferences.excludeOrgs,
40153
+ aiPolicyBlocklist: this.state.preferences.aiPolicyBlocklist,
40047
40154
  anchorThreshold: options?.anchorThreshold ?? this.state.preferences.featuresAnchorThreshold,
40048
40155
  splitRatio: options?.splitRatio ?? this.state.preferences.featuresSplitRatio
40049
40156
  });
@@ -40585,6 +40692,7 @@ var FIELD_CONFIGS = {
40585
40692
  scope: { type: "enum-array", validValues: IssueScopeSchema.options },
40586
40693
  excludeRepos: { type: "array" },
40587
40694
  excludeOrgs: { type: "array" },
40695
+ preferredOrgs: { type: "array" },
40588
40696
  aiPolicyBlocklist: { type: "array" },
40589
40697
  projectCategories: {
40590
40698
  type: "enum-array",
@@ -40746,9 +40854,9 @@ function registerTools(server, scout) {
40746
40854
  "search",
40747
40855
  "Search for open source issues matching your preferences",
40748
40856
  {
40749
- maxResults: external_exports3.number().int().min(1).max(50).optional().describe("Maximum number of results to return (1-50, default 10)"),
40857
+ maxResults: external_exports3.number().int().min(1).max(100).optional().describe("Maximum number of results to return (1-100, default 10)"),
40750
40858
  strategies: external_exports3.string().optional().describe(
40751
- "Comma-separated search strategies: merged, starred, broad, maintained, all"
40859
+ "Comma-separated search strategies: merged, orgs, starred, broad, maintained, all"
40752
40860
  ),
40753
40861
  preferLanguages: external_exports3.array(external_exports3.string()).optional().describe(
40754
40862
  "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."
@@ -41095,7 +41203,7 @@ function registerResources(server, scout) {
41095
41203
  // src/index.ts
41096
41204
  var SERVER_INFO = {
41097
41205
  name: "oss-scout-mcp",
41098
- version: "0.10.1"
41206
+ version: "0.11.0"
41099
41207
  // x-release-please-version
41100
41208
  };
41101
41209
  function createServer(scout) {
package/dist/tools.js CHANGED
@@ -35,13 +35,16 @@ export function registerTools(server, scout) {
35
35
  .number()
36
36
  .int()
37
37
  .min(1)
38
- .max(50)
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-50, default 10)"),
43
+ .describe("Maximum number of results to return (1-100, default 10)"),
41
44
  strategies: z
42
45
  .string()
43
46
  .optional()
44
- .describe("Comma-separated search strategies: merged, starred, broad, maintained, all"),
47
+ .describe("Comma-separated search strategies: merged, orgs, starred, broad, maintained, all"),
45
48
  preferLanguages: z
46
49
  .array(z.string())
47
50
  .optional()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-scout/mcp",
3
- "version": "0.10.1",
3
+ "version": "0.11.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": "^1.5.1"
21
+ "@oss-scout/core": "^1.6.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^25.5.0",