@index9/mcp 6.0.0 → 6.2.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/cli.js CHANGED
@@ -43,6 +43,16 @@ var Index9MetaSchema = z.object({
43
43
  retryAfterSeconds: z.number().optional(),
44
44
  rateLimit: RateLimitMetaSchema.optional()
45
45
  });
46
+ var MissingModelDiagnosticSchema = z.object({
47
+ reason: z.enum(["unknown_provider", "no_match", "suggestions_available", "ambiguous_alias"]),
48
+ provider: z.string().optional(),
49
+ message: z.string()
50
+ });
51
+ var SuggestionEntrySchema = z.object({
52
+ id: z.string(),
53
+ name: z.string(),
54
+ created: z.number().nullable()
55
+ });
46
56
  var UserContentTextPartSchema = z.strictObject({
47
57
  type: z.literal("text"),
48
58
  text: z.string().trim().min(1)
@@ -101,9 +111,11 @@ Typical workflow:
101
111
  Key rules:
102
112
  - find_models requires \`q\` when \`sortBy=relevance\` (the default). Omit \`q\` only with \`sortBy=created\` or \`sortBy=price\`.
103
113
  - find_models price-asc tends to be dominated by free preview models \u2014 pass \`excludeFree=true\` when you want a paid SLA.
104
- - find_models flags \`meta.confidence: "low"\` when no candidate matched on keyword (BM25). When that fires, prefer \`meta.suggestion\` over the returned scores; weak hits are capped at score=30 so they don't masquerade as strong matches.
105
- - get_models accepts aliases (display names, short names) \u2014 not just full IDs. Unknown ids return in missingIds with \`suggestions\` (token-fuzzy or recency-anchored newest-from-provider). Retry with one of the suggested ids.
114
+ - find_models always emits \`meta.confidence\` ("high" | "low") on semantic queries. Low means no candidate matched on keyword (BM25); \`meta.lowConfidenceReason\` is "no_keyword_matches" or "no_results" and \`meta.suggestion\` carries an actionable hint. Weak hits are capped at score=30 so they don't masquerade as strong matches. Pass \`requireKeywordMatch: true\` to get an empty page instead of weak vector-only neighbors.
115
+ - find_models with sortBy=price exposes \`pricing.effectivePromptPerMillion\` and \`pageInfo.priceSortBasis\` \u2014 sort order may diverge from displayed promptPerMillion for models with per-request fees.
116
+ - Your training-data model IDs are routinely stale. get_models / compare_models / test_model all accept aliases (display names, short names) and return unknown ids in \`missingIds\` with \`suggestions[id]\` ordered newest-first (each entry: \`{id, name, created}\`, where \`created\` is unix seconds), plus \`missingDiagnostics[id].reason\` \u2208 {"unknown_provider", "no_match", "suggestions_available", "ambiguous_alias"}. **Default recovery: retry with \`suggestions[id][0].id\` \u2014 it's the newest viable replacement.** If suggestions is empty or reason="no_match"/"unknown_provider", fall back to \`find_models sortBy=created\` instead.
106
117
  - compare_models accepts the same alias formats as get_models. Use it instead of N parallel get_models calls when the user is comparing finalists.
118
+ - test_model pre-flight resolves and filters unresolvable ids out of the OpenRouter call, so stale ids never cost you credits \u2014 they come back in missingIds with the same suggestions/diagnostics surface as get_models. If every id is unresolvable, the call returns 400 with diagnostics and no inference fires.
107
119
  - Use test_model with \`dryRun=true\` to estimate cost before live testing. Pass \`expectedPromptTokens\` for capacity planning at sizes you don't want to paste in full.
108
120
  - test_model with \`dryRun=false\` (default) requires OPENROUTER_API_KEY and incurs real usage costs.
109
121
  - Reasoning-capable models (capabilities includes "reasoning") burn hidden reasoning tokens against \`maxTokens\` before emitting visible text. Leave \`maxTokens\` unset, or set it to at least 2000, when testing reasoning models \u2014 otherwise results may fail with finish_reason=length.
@@ -129,11 +141,11 @@ Examples:
129
141
 
130
142
  Valid capabilities: ${CAPABILITIES.join(", ")}.
131
143
 
132
- Each result: id, name, description, created (unix seconds), createdAt (ISO 8601), contextLength, maxOutputTokens, pricing.{promptPerMillion, completionPerMillion} (numbers, USD per million tokens), inputModalities[] / outputModalities[] (e.g. ["text","image"] \u2014 check at a glance to spot text-only vs multimodal models), capabilities[], score.
144
+ Each result: id, name, description, created (unix seconds), createdAt (ISO 8601), contextLength, maxOutputTokens, pricing.{promptPerMillion, completionPerMillion} (rounded display $/M), pricing.{promptPerToken, completionPerToken, requestUsd} (exact, use for cost math), inputModalities[] / outputModalities[], capabilities[], score. With sortBy=price, results also expose pricing.effectivePromptPerMillion and pageInfo.priceSortBasis \u2014 sort order may diverge from displayed promptPerMillion for models with per-request fees.
133
145
 
134
146
  \`score\` is 0-100: the best match per page scores 100; others scale proportionally. Combines semantic similarity and keyword matching. Null when sorting by price or date.
135
147
 
136
- \`q\` must be at least 2 characters when provided. \`meta.confidence\` is "low" when no candidate matched on keyword (BM25), meaning the ranker fell back to vector similarity alone \u2014 typo, gibberish, or a query the catalog can't answer. When low, \`meta.suggestion\` carries an actionable hint and \`score\` values are capped at 30 so weak hits don't masquerade as strong ones.
148
+ \`q\` must be at least 2 characters when provided. For semantic queries, \`meta.confidence\` is always emitted as "high" or "low". Low means no candidate matched on keyword (BM25); \`meta.lowConfidenceReason\` is "no_keyword_matches" or "no_results" and \`meta.suggestion\` carries an actionable hint. Pass \`requireKeywordMatch: true\` to suppress weak hits and get an empty page on low confidence.
137
149
 
138
150
  Pass result.id to get_models for full specs or to test_model for live testing.`,
139
151
  requiresKey: false
@@ -146,16 +158,18 @@ Pass result.id to get_models for full specs or to test_model for live testing.`,
146
158
 
147
159
  Call after find_models to inspect candidates, or directly when the user names a model (format: 'provider/model-name').
148
160
 
149
- Response: { results: (Model | null)[], missingIds: string[], resolvedAliases?: Record<alias, canonicalId>, ambiguousAliases?: Record<alias, candidateIds[]>, suggestions?: Record<unknownId, candidateIds[]> }. Each non-null result has:
161
+ Response: { results: (Model | null)[], missingIds: string[], resolvedAliases?: Record<alias, canonicalId>, ambiguousAliases?: Record<alias, candidateIds[]>, suggestions?: Record<unknownId, Array<{id, name, created}>> }. Each non-null result has:
150
162
  - id, canonicalSlug, name, description
151
163
  - created (unix seconds), createdAt (ISO 8601), knowledgeCutoff (ISO date or null)
152
164
  - contextLength (tokens), maxOutputTokens, isModerated
153
- - pricing: { promptPerMillion, completionPerMillion, requestUsd, imageUsd } \u2014 all USD, all numbers. Token prices are per million tokens; request/image are per unit.
165
+ - pricing: { promptPerMillion, completionPerMillion, promptPerToken, completionPerToken, requestUsd, imageUsd } \u2014 *PerMillion is rounded display, *PerToken is exact (use for cost math). request/image are flat per-unit fees.
154
166
  - architecture: { inputModalities[], outputModalities[], tokenizer, instructType }
155
167
  - capabilities[]: normalized capability flags (same values as find_models and capabilitiesAll/Any)
156
168
  - supportedParameters[]: OpenRouter parameters the model accepts (e.g., "temperature", "tools", "response_format")
157
169
 
158
- Entries in results are null when the id is unknown; those ids appear in missingIds. Ambiguous aliases appear in ambiguousAliases with candidate canonical ids \u2014 pass a canonical id to disambiguate. Unknown ids that partially match (e.g. "sonnet" \u2192 all Claude Sonnet variants) appear in suggestions with up to 5 candidate ids. When token-overlap finds nothing but the id is shaped like \`provider/<unknown>\` and the provider exists, suggestions falls back to the 5 newest models from that provider (real created timestamps, no hardcoded "popular" list). Retry with one of the suggested ids.`,
170
+ Entries in results are null when the id is unknown; those ids appear in missingIds. Ambiguous aliases appear in ambiguousAliases with candidate canonical ids \u2014 pass a canonical id to disambiguate. Unknown ids that partially match (e.g. "sonnet" \u2192 all Claude Sonnet variants) appear in \`suggestions\` as up to 5 \`{id, name, created}\` entries **sorted newest-first** \u2014 pick \`suggestions[id][0].id\` for the most current replacement without a second lookup. When token-overlap finds nothing but the id is shaped like \`provider/<unknown>\` and the provider exists, suggestions falls back to the 5 newest models from that provider (real created timestamps, no hardcoded "popular" list).
171
+
172
+ \`missingDiagnostics\` (when present) gives a machine-readable reason per missing id: \`unknown_provider\` (the prefix before / isn't in the catalog \u2014 fix the provider, not the model name), \`ambiguous_alias\`, \`suggestions_available\` (mirrors suggestions[id]), or \`no_match\`.`,
159
173
  requiresKey: false
160
174
  },
161
175
  compare_models: {
@@ -166,13 +180,13 @@ Entries in results are null when the id is unknown; those ids appear in missingI
166
180
 
167
181
  Use this when the user asks "which is cheaper / has more context / supports X" across multiple specific models. Faster than calling get_models and diffing yourself.
168
182
 
169
- Response: { models: ModelResponse[], diff: { contextLength, maxOutputTokens, promptPricePerMillion, completionPricePerMillion, tokenizer, inputModalities, outputModalities, capabilities, supportedParameters }, cheapestForPromptPerMillion, largestContext, missingIds, resolvedAliases?, ambiguousAliases?, suggestions? }.
183
+ Response: { models: ModelResponse[], diff: { contextLength, maxOutputTokens, promptPricePerMillion, completionPricePerMillion, tokenizer, inputModalities, outputModalities, capabilities, supportedParameters }, cheapestForPromptPerMillion, largestContext, missingIds, resolvedAliases?, ambiguousAliases?, suggestions?: Record<unknownId, Array<{id, name, created}>> (newest-first), missingDiagnostics? }.
170
184
 
171
185
  Each numeric/string diff field has { allEqual: boolean, values: Record<id, value|null> }. Capability/parameter diffs have { commonAll: string[], uniquePerModel: Record<id, string[]> }. cheapestForPromptPerMillion / largestContext are convenience picks across the supplied models \u2014 null when the field is missing on every model.
172
186
 
173
- Optional: pass \`expectedPromptTokens\` AND \`expectedCompletionTokens\` to also receive \`workloadCosts\` (per-model totalCostUsd) and \`cheapestForRealisticWorkload\` \u2014 the actual cheapest given the user's expected token mix. This matters when prompt:completion price ratios diverge across models (e.g., a model with cheap prompt but expensive completion can lose against a flatter-priced sibling under heavy completions).
187
+ Optional: pass \`expectedPromptTokens\` AND \`expectedCompletionTokens\` to also receive \`workloadCosts\` and \`cheapestForRealisticWorkload\` \u2014 the actual cheapest given the user's expected token mix. Each \`workloadCosts[i]\` carries \`tokenCostUsd\` (token-only), \`requestCostUsd\` (per-request fee), \`totalCostUsd\` (sum, includes request fees), and \`pricingBasis\` ("exact_per_token" | "rounded_per_million" | "unavailable"). This matters when prompt:completion price ratios diverge across models, or when a model has a per-request fee.
174
188
 
175
- Accepts the same alias formats as get_models. Unknown ids are returned in missingIds (with suggestions when partial matches exist).`,
189
+ Accepts the same alias formats as get_models. Unknown ids are returned in missingIds (with \`suggestions[id]\` as newest-first \`{id, name, created}\` entries when partial matches exist, plus \`missingDiagnostics\` carrying a machine-readable reason per id). When fewer than 2 ids resolve, this returns 400 with the diagnostics so you can retry with \`suggestions[id][0].id\` for each missing id.`,
176
190
  requiresKey: false
177
191
  },
178
192
  list_facets: {
@@ -206,7 +220,11 @@ Parameters:
206
220
  - expectedCompletionTokens: Optional completion token estimate used by dryRun
207
221
  - maxTokens, systemPrompt, temperature, topP, seed, responseFormat, enforceJson, retries: Live-testing controls (ignored when dryRun=true)
208
222
 
209
- Results (live): each result carries modelId (the id you passed), resolvedModelId (canonical id, present when the input was an alias), ok, response, latencyMs, tokens { prompt, completion }, cost (USD; live from OpenRouter when available, else estimated from cached pricing), and truncated=true when finish_reason is "length". Use find_models or get_models first to identify model ids.`,
223
+ Results (live): each result carries modelId (the id you passed), resolvedModelId (canonical id, present when the input was an alias), ok, response, latencyMs, tokens { prompt, completion }, cost (USD; live from OpenRouter when available, else estimated from cached pricing), and truncated=true when finish_reason is "length". On failure, results include \`error\` (free-form) plus \`failureReason\` ("insufficient_credits" | "model_unavailable" | "rate_limited" | "timeout" | "invalid_request" | "unknown") so callers can pick a retry strategy without parsing the error string.
224
+
225
+ Results (dryRun): each entry carries \`tokenCostUsd\`, \`requestCostUsd\`, \`totalCostUsd\` (matches \`estimatedCost\`, includes per-request fees), and \`estimatedCostBasis\` (same enum as compare_models.workloadCosts). Use find_models or get_models first to identify model ids.
226
+
227
+ Stale-id recovery: unresolvable model ids are filtered out **before** any OpenRouter call (so they cost nothing) and returned in \`missingIds\` alongside \`suggestions\` (newest-first \`{id, name, created}\` entries), \`resolvedAliases\`, \`ambiguousAliases\`, and \`missingDiagnostics\` \u2014 same shape as get_models / compare_models. If every id is unresolvable, the call returns 400 with diagnostics and no inference fires. Default recovery: retry with \`suggestions[id][0].id\`.`,
210
228
  requiresKey: true
211
229
  }
212
230
  };
@@ -219,6 +237,7 @@ var PARAM_DESCRIPTIONS = {
219
237
  modality: `Required output modality. Filters on the model's output modalities, not input capabilities. For example, "image" finds image-generation models, while capabilitiesAll=["vision"] finds models that accept image input. Valid values: ${OUTPUT_MODALITIES.join(", ")}.`,
220
238
  provider: `Provider prefix filter. Array of provider slugs \u2014 a model matches if its ID starts with any of them (e.g., ['openai'] matches 'openai/gpt-4o'; ['openai','anthropic'] matches both). Pass a single-element array for one provider. Common providers: ${COMMON_PROVIDERS.join(", ")}.`,
221
239
  excludeFree: `When true, exclude models with id ending in ':free'. Useful for sortBy=price (which would otherwise be dominated by free-tier preview models) and when you want a paid SLA. Default false.`,
240
+ requireKeywordMatch: `When true, suppress weak vector-only results from semantic queries. If no candidate has a BM25 keyword hit, returns an empty page with meta.confidence='low' and meta.lowConfidenceReason \u2014 instead of returning misleading nearest-neighbor matches. Filter-only queries (sortBy=created or sortBy=price without q) ignore this flag. Default false.`,
222
241
  expectedPromptTokens: `Expected number of prompt tokens for dryRun cost estimation. When set, overrides the heuristic that counts characters from the literal \`prompt\` string \u2014 use this for capacity planning ("what would 6000-token reviews cost?") without pasting filler. If both are omitted, the prompt string is tokenized at ~4 chars/token.`,
223
242
  expectedCompletionTokens: `Expected number of completion tokens for cost estimation (default: 256). Typical ranges: 100-500 for quick tests, 1000-2000 for code generation, 4000+ for long-form content. This is a heuristic \u2014 actual billed tokens may differ.`
224
243
  };
@@ -556,7 +575,8 @@ var SearchQuerySchema = z2.object({
556
575
  capabilitiesAny: z2.array(z2.enum(CAPABILITIES)).optional(),
557
576
  modality: z2.enum(OUTPUT_MODALITIES).optional(),
558
577
  provider: z2.array(z2.string().min(1)).optional(),
559
- excludeFree: z2.boolean().optional()
578
+ excludeFree: z2.boolean().optional(),
579
+ requireKeywordMatch: z2.boolean().optional()
560
580
  }).strict();
561
581
  var SearchResultSchema = z2.object({
562
582
  id: z2.string(),
@@ -568,7 +588,11 @@ var SearchResultSchema = z2.object({
568
588
  maxOutputTokens: z2.number().nullable(),
569
589
  pricing: z2.object({
570
590
  promptPerMillion: z2.number().nullable(),
571
- completionPerMillion: z2.number().nullable()
591
+ completionPerMillion: z2.number().nullable(),
592
+ promptPerToken: z2.number().nullable().optional(),
593
+ completionPerToken: z2.number().nullable().optional(),
594
+ requestUsd: z2.number().nullable().optional(),
595
+ effectivePromptPerMillion: z2.number().nullable().optional()
572
596
  }),
573
597
  inputModalities: z2.array(z2.string()),
574
598
  outputModalities: z2.array(z2.string()),
@@ -582,13 +606,15 @@ var SearchResponseSchema = z2.object({
582
606
  limit: z2.number(),
583
607
  hasMore: z2.boolean(),
584
608
  sortBy: SearchSortBySchema,
585
- sortOrder: SearchSortOrderSchema
609
+ sortOrder: SearchSortOrderSchema,
610
+ priceSortBasis: z2.literal("effective_prompt_per_million").optional()
586
611
  }),
587
612
  meta: z2.object({
588
613
  queryMode: z2.enum(["semantic", "filter_only"]),
589
614
  ranking: z2.literal("hybrid_rrf"),
590
615
  confidence: z2.enum(["high", "low"]).optional(),
591
- suggestion: z2.string().optional()
616
+ suggestion: z2.string().optional(),
617
+ lowConfidenceReason: z2.enum(["no_keyword_matches", "no_results"]).optional()
592
618
  })
593
619
  });
594
620
  var FindModelsToolResultSchema = SearchResponseSchema.extend({
@@ -604,6 +630,8 @@ var BatchModelLookupRequestSchema = z3.object({
604
630
  var ModelPricingSchema = z3.object({
605
631
  promptPerMillion: z3.number().nullable(),
606
632
  completionPerMillion: z3.number().nullable(),
633
+ promptPerToken: z3.number().nullable().optional(),
634
+ completionPerToken: z3.number().nullable().optional(),
607
635
  requestUsd: z3.number().nullable(),
608
636
  imageUsd: z3.number().nullable()
609
637
  });
@@ -634,19 +662,22 @@ var BatchModelLookupResponseSchema = z3.object({
634
662
  missingIds: z3.array(z3.string()),
635
663
  resolvedAliases: z3.record(z3.string(), z3.string()).optional(),
636
664
  ambiguousAliases: z3.record(z3.string(), z3.array(z3.string())).optional(),
637
- suggestions: z3.record(z3.string(), z3.array(z3.string())).optional()
665
+ suggestions: z3.record(z3.string(), z3.array(SuggestionEntrySchema)).optional(),
666
+ missingDiagnostics: z3.record(z3.string(), MissingModelDiagnosticSchema).optional()
638
667
  }).strict();
639
668
  var GetModelsToolResultSchema = z3.object({
640
669
  results: z3.array(ModelResponseSchema.nullable()),
641
670
  missingIds: z3.array(z3.string()),
642
671
  resolvedAliases: z3.record(z3.string(), z3.string()).optional(),
643
672
  ambiguousAliases: z3.record(z3.string(), z3.array(z3.string())).optional(),
644
- suggestions: z3.record(z3.string(), z3.array(z3.string())).optional(),
673
+ suggestions: z3.record(z3.string(), z3.array(SuggestionEntrySchema)).optional(),
674
+ missingDiagnostics: z3.record(z3.string(), MissingModelDiagnosticSchema).optional(),
645
675
  _index9: Index9MetaSchema
646
676
  });
647
677
 
648
678
  // ../core/dist/schemas/compare.js
649
679
  import { z as z4 } from "zod";
680
+ var PricingBasisSchema = z4.enum(["exact_per_token", "rounded_per_million", "unavailable"]);
650
681
  var CompareRequestSchema = z4.object({
651
682
  ids: z4.array(z4.string().min(1)).min(2, "compare requires at least 2 ids").max(LIMITS.compareModelsMax, `ids must contain between 2 and ${LIMITS.compareModelsMax} model IDs`),
652
683
  expectedPromptTokens: z4.number().int().positive().optional(),
@@ -683,7 +714,10 @@ var CompareWorkloadCostSchema = z4.object({
683
714
  modelId: z4.string(),
684
715
  promptTokens: z4.number().int().nonnegative(),
685
716
  completionTokens: z4.number().int().nonnegative(),
686
- totalCostUsd: z4.number().nullable()
717
+ totalCostUsd: z4.number().nullable(),
718
+ tokenCostUsd: z4.number().nullable().optional(),
719
+ requestCostUsd: z4.number().nullable().optional(),
720
+ pricingBasis: PricingBasisSchema.optional()
687
721
  });
688
722
  var CompareResponseSchema = z4.object({
689
723
  models: z4.array(ModelResponseSchema),
@@ -694,8 +728,9 @@ var CompareResponseSchema = z4.object({
694
728
  workloadCosts: z4.array(CompareWorkloadCostSchema).optional(),
695
729
  resolvedAliases: z4.record(z4.string(), z4.string()).optional(),
696
730
  missingIds: z4.array(z4.string()),
697
- suggestions: z4.record(z4.string(), z4.array(z4.string())).optional(),
698
- ambiguousAliases: z4.record(z4.string(), z4.array(z4.string())).optional()
731
+ suggestions: z4.record(z4.string(), z4.array(SuggestionEntrySchema)).optional(),
732
+ ambiguousAliases: z4.record(z4.string(), z4.array(z4.string())).optional(),
733
+ missingDiagnostics: z4.record(z4.string(), MissingModelDiagnosticSchema).optional()
699
734
  }).strict();
700
735
  var CompareModelsToolResultSchema = CompareResponseSchema.extend({
701
736
  _index9: Index9MetaSchema
@@ -770,8 +805,17 @@ var TestPricingUsedSchema = z6.object({
770
805
  promptPerToken: z6.number().nullable().optional(),
771
806
  completionPerToken: z6.number().nullable().optional(),
772
807
  promptPerMillion: z6.number().nullable().optional(),
773
- completionPerMillion: z6.number().nullable().optional()
808
+ completionPerMillion: z6.number().nullable().optional(),
809
+ requestUsd: z6.number().nullable().optional()
774
810
  });
811
+ var TestFailureReasonSchema = z6.enum([
812
+ "insufficient_credits",
813
+ "model_unavailable",
814
+ "rate_limited",
815
+ "timeout",
816
+ "invalid_request",
817
+ "unknown"
818
+ ]);
775
819
  var TestModelMetadataSchema = z6.object({
776
820
  id: z6.string(),
777
821
  name: z6.string(),
@@ -796,6 +840,7 @@ var TestResultFailureSchema = z6.object({
796
840
  ok: z6.literal(false),
797
841
  model: TestModelMetadataSchema,
798
842
  error: z6.string(),
843
+ failureReason: TestFailureReasonSchema.optional(),
799
844
  latencyMs: z6.number().min(0)
800
845
  });
801
846
  var TestResultSchema = z6.discriminatedUnion("ok", [
@@ -807,15 +852,28 @@ var TestEstimateResultSchema = z6.object({
807
852
  resolvedModelId: z6.string().optional(),
808
853
  model: TestModelMetadataSchema,
809
854
  tokens: UsageTokensSchema,
810
- estimatedCost: z6.number().nullable().optional()
855
+ estimatedCost: z6.number().nullable().optional(),
856
+ tokenCostUsd: z6.number().nullable().optional(),
857
+ requestCostUsd: z6.number().nullable().optional(),
858
+ totalCostUsd: z6.number().nullable().optional(),
859
+ estimatedCostBasis: PricingBasisSchema.optional()
811
860
  });
861
+ var TestResolutionFieldsSchema = {
862
+ missingIds: z6.array(z6.string()).optional(),
863
+ resolvedAliases: z6.record(z6.string(), z6.string()).optional(),
864
+ ambiguousAliases: z6.record(z6.string(), z6.array(z6.string())).optional(),
865
+ suggestions: z6.record(z6.string(), z6.array(SuggestionEntrySchema)).optional(),
866
+ missingDiagnostics: z6.record(z6.string(), MissingModelDiagnosticSchema).optional()
867
+ };
812
868
  var TestDryRunResponseSchema = z6.object({
813
869
  dryRun: z6.literal(true),
814
870
  results: z6.array(TestEstimateResultSchema),
815
- disclaimer: z6.string()
871
+ disclaimer: z6.string(),
872
+ ...TestResolutionFieldsSchema
816
873
  });
817
874
  var TestLiveResponseSchema = z6.object({
818
- results: z6.array(TestResultSchema)
875
+ results: z6.array(TestResultSchema),
876
+ ...TestResolutionFieldsSchema
819
877
  });
820
878
  var TestResponseSchema = z6.union([TestDryRunResponseSchema, TestLiveResponseSchema]);
821
879
 
@@ -963,6 +1021,22 @@ function extractError(body) {
963
1021
  }
964
1022
  return "Request failed";
965
1023
  }
1024
+ var RECOVERY_FIELDS = [
1025
+ "missingIds",
1026
+ "resolvedAliases",
1027
+ "ambiguousAliases",
1028
+ "suggestions",
1029
+ "missingDiagnostics"
1030
+ ];
1031
+ function extractRecoveryFields(body) {
1032
+ if (typeof body !== "object" || body === null || Array.isArray(body)) return {};
1033
+ const out = {};
1034
+ const b = body;
1035
+ for (const key of RECOVERY_FIELDS) {
1036
+ if (key in b) out[key] = b[key];
1037
+ }
1038
+ return out;
1039
+ }
966
1040
  async function callApi(ctx, url, options, responseSchema) {
967
1041
  const res = await fetchWithRetry(url, options);
968
1042
  let body;
@@ -973,7 +1047,12 @@ async function callApi(ctx, url, options, responseSchema) {
973
1047
  }
974
1048
  if (!res.ok) {
975
1049
  return toResponse(
976
- { error: extractError(body), status: res.status, _index9: buildMeta(ctx, res.headers) },
1050
+ {
1051
+ error: extractError(body),
1052
+ status: res.status,
1053
+ ...extractRecoveryFields(body),
1054
+ _index9: buildMeta(ctx, res.headers)
1055
+ },
977
1056
  true
978
1057
  );
979
1058
  }
@@ -1005,6 +1084,7 @@ async function handleSearchModels(ctx, args) {
1005
1084
  if (q.modality) params.modality = q.modality;
1006
1085
  if (q.provider?.length) params.provider = q.provider.join(",");
1007
1086
  if (q.excludeFree === true) params.excludeFree = "true";
1087
+ if (q.requireKeywordMatch === true) params.requireKeywordMatch = "true";
1008
1088
  return callApi(
1009
1089
  ctx,
1010
1090
  buildUrl(ctx.baseUrl, API_PATHS.search, params),
@@ -1020,7 +1100,11 @@ async function handleGetModels(ctx, args) {
1020
1100
  return callApi(
1021
1101
  ctx,
1022
1102
  `${ctx.baseUrl}${API_PATHS.model}`,
1023
- { method: "POST", headers: baseHeaders(ctx), body: JSON.stringify(parsed.data) },
1103
+ {
1104
+ method: "POST",
1105
+ headers: baseHeaders(ctx),
1106
+ body: JSON.stringify(parsed.data)
1107
+ },
1024
1108
  BatchModelLookupResponseSchema
1025
1109
  );
1026
1110
  }
@@ -1032,7 +1116,11 @@ async function handleCompareModels(ctx, args) {
1032
1116
  return callApi(
1033
1117
  ctx,
1034
1118
  `${ctx.baseUrl}${API_PATHS.compare}`,
1035
- { method: "POST", headers: baseHeaders(ctx), body: JSON.stringify(parsed.data) },
1119
+ {
1120
+ method: "POST",
1121
+ headers: baseHeaders(ctx),
1122
+ body: JSON.stringify(parsed.data)
1123
+ },
1036
1124
  CompareResponseSchema
1037
1125
  );
1038
1126
  }
@@ -1096,7 +1184,8 @@ async function createServer() {
1096
1184
  capabilitiesAny: z7.array(z7.enum(CAPABILITIES)).optional().describe(PARAM_DESCRIPTIONS.capabilitiesAny),
1097
1185
  modality: z7.enum(OUTPUT_MODALITIES).optional().describe(PARAM_DESCRIPTIONS.modality),
1098
1186
  provider: z7.array(z7.string().min(1)).optional().describe(PARAM_DESCRIPTIONS.provider),
1099
- excludeFree: z7.boolean().optional().describe(PARAM_DESCRIPTIONS.excludeFree)
1187
+ excludeFree: z7.boolean().optional().describe(PARAM_DESCRIPTIONS.excludeFree),
1188
+ requireKeywordMatch: z7.boolean().optional().describe(PARAM_DESCRIPTIONS.requireKeywordMatch)
1100
1189
  },
1101
1190
  outputSchema: FindModelsToolResultSchema.shape,
1102
1191
  annotations: { readOnlyHint: true }
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": "0.3",
3
3
  "name": "index9",
4
- "version": "5.3.0",
4
+ "version": "6.1.0",
5
5
  "description": "Discover, shortlist, compare, cost-model, and live-test 300+ AI models from your editor",
6
6
  "author": {
7
7
  "name": "Index9"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@index9/mcp",
3
- "version": "6.0.0",
3
+ "version": "6.2.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,11 +24,11 @@
24
24
  "zod": "^4.4.3"
25
25
  },
26
26
  "devDependencies": {
27
- "@types/node": "^25.6.1",
27
+ "@types/node": "^25.6.2",
28
28
  "tsup": "^8.5.1",
29
29
  "typescript": "6.0.3",
30
- "vitest": "^4.1.5",
31
- "@index9/core": "2.3.2"
30
+ "vitest": "^4.1.6",
31
+ "@index9/core": "2.5.0"
32
32
  },
33
33
  "engines": {
34
34
  "node": ">=20"