@index9/mcp 5.3.0 → 6.1.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 +145 -123
- package/manifest.json +1 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -43,6 +43,11 @@ 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
|
+
});
|
|
46
51
|
var UserContentTextPartSchema = z.strictObject({
|
|
47
52
|
type: z.literal("text"),
|
|
48
53
|
text: z.string().trim().min(1)
|
|
@@ -101,8 +106,9 @@ Typical workflow:
|
|
|
101
106
|
Key rules:
|
|
102
107
|
- find_models requires \`q\` when \`sortBy=relevance\` (the default). Omit \`q\` only with \`sortBy=created\` or \`sortBy=price\`.
|
|
103
108
|
- 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
|
|
105
|
-
-
|
|
109
|
+
- 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.
|
|
110
|
+
- 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.
|
|
111
|
+
- 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) and \`missingDiagnostics\` keyed by id with \`reason\` ("unknown_provider" | "no_match" | "suggestions_available" | "ambiguous_alias") so retry strategy is explicit. Retry with one of the suggested ids.
|
|
106
112
|
- 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.
|
|
107
113
|
- 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
114
|
- test_model with \`dryRun=false\` (default) requires OPENROUTER_API_KEY and incurs real usage costs.
|
|
@@ -129,11 +135,11 @@ Examples:
|
|
|
129
135
|
|
|
130
136
|
Valid capabilities: ${CAPABILITIES.join(", ")}.
|
|
131
137
|
|
|
132
|
-
Each result: id, name, description, created (unix seconds), createdAt (ISO 8601), contextLength, maxOutputTokens, pricing.{promptPerMillion, completionPerMillion} (
|
|
138
|
+
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
139
|
|
|
134
140
|
\`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
141
|
|
|
136
|
-
\`q\` must be at least 2 characters when provided. \`meta.confidence\` is "low"
|
|
142
|
+
\`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
143
|
|
|
138
144
|
Pass result.id to get_models for full specs or to test_model for live testing.`,
|
|
139
145
|
requiresKey: false
|
|
@@ -150,12 +156,14 @@ Response: { results: (Model | null)[], missingIds: string[], resolvedAliases?: R
|
|
|
150
156
|
- id, canonicalSlug, name, description
|
|
151
157
|
- created (unix seconds), createdAt (ISO 8601), knowledgeCutoff (ISO date or null)
|
|
152
158
|
- contextLength (tokens), maxOutputTokens, isModerated
|
|
153
|
-
- pricing: { promptPerMillion, completionPerMillion, requestUsd, imageUsd } \u2014
|
|
159
|
+
- 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
160
|
- architecture: { inputModalities[], outputModalities[], tokenizer, instructType }
|
|
155
161
|
- capabilities[]: normalized capability flags (same values as find_models and capabilitiesAll/Any)
|
|
156
162
|
- supportedParameters[]: OpenRouter parameters the model accepts (e.g., "temperature", "tools", "response_format")
|
|
157
163
|
|
|
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
|
|
164
|
+
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.
|
|
165
|
+
|
|
166
|
+
\`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
167
|
requiresKey: false
|
|
160
168
|
},
|
|
161
169
|
compare_models: {
|
|
@@ -170,9 +178,9 @@ Response: { models: ModelResponse[], diff: { contextLength, maxOutputTokens, pro
|
|
|
170
178
|
|
|
171
179
|
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
180
|
|
|
173
|
-
Optional: pass \`expectedPromptTokens\` AND \`expectedCompletionTokens\` to also receive \`workloadCosts\`
|
|
181
|
+
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
182
|
|
|
175
|
-
Accepts the same alias formats as get_models. Unknown ids are returned in missingIds (with suggestions when partial matches exist).`,
|
|
183
|
+
Accepts the same alias formats as get_models. Unknown ids are returned in missingIds (with suggestions when partial matches exist, plus \`missingDiagnostics\` carrying a machine-readable reason per id).`,
|
|
176
184
|
requiresKey: false
|
|
177
185
|
},
|
|
178
186
|
list_facets: {
|
|
@@ -206,7 +214,9 @@ Parameters:
|
|
|
206
214
|
- expectedCompletionTokens: Optional completion token estimate used by dryRun
|
|
207
215
|
- maxTokens, systemPrompt, temperature, topP, seed, responseFormat, enforceJson, retries: Live-testing controls (ignored when dryRun=true)
|
|
208
216
|
|
|
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".
|
|
217
|
+
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.
|
|
218
|
+
|
|
219
|
+
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.`,
|
|
210
220
|
requiresKey: true
|
|
211
221
|
}
|
|
212
222
|
};
|
|
@@ -219,6 +229,7 @@ var PARAM_DESCRIPTIONS = {
|
|
|
219
229
|
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
230
|
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
231
|
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.`,
|
|
232
|
+
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
233
|
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
234
|
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
235
|
};
|
|
@@ -235,184 +246,167 @@ var SITE = {
|
|
|
235
246
|
},
|
|
236
247
|
hero: {
|
|
237
248
|
titleLine1: "Pick the right AI model",
|
|
238
|
-
titleLine2: "
|
|
239
|
-
subtitle: "Index9 is an MCP server. Your
|
|
240
|
-
proof: ["Live OpenRouter data
|
|
249
|
+
titleLine2: "from chat",
|
|
250
|
+
subtitle: "Index9 is an MCP server. Your coding assistant uses it to search, compare, and live-test 300+ models on the task you're working on, so it recommends the best fit.",
|
|
251
|
+
proof: ["Live OpenRouter data \xB7 300+ models \xB7 refreshed every 30 min"],
|
|
241
252
|
pricingNote: "Free. You only pay OpenRouter for live model calls.",
|
|
242
253
|
getStarted: "Add index9 to your editor",
|
|
243
254
|
seeHowItWorks: "See a real session",
|
|
244
255
|
updatedBadge: "OpenRouter data \xB7 refreshed "
|
|
245
256
|
},
|
|
257
|
+
problem: {
|
|
258
|
+
label: "Why this exists",
|
|
259
|
+
heading: "Your assistant's model knowledge is stale",
|
|
260
|
+
body: [
|
|
261
|
+
'New models ship every week. Pricing changes. "Use GPT-4" or "use Claude 3.5" is usually months behind reality.',
|
|
262
|
+
"Without live data, your assistant defaults to whatever it learned in training \u2014 often a model that's been superseded by something cheaper or better-suited to your task.",
|
|
263
|
+
"Index9 gives it the data and the tools to actually compare."
|
|
264
|
+
]
|
|
265
|
+
},
|
|
246
266
|
howItWorks: {
|
|
247
267
|
label: "How it works",
|
|
248
|
-
heading: "
|
|
249
|
-
subtitle: "Index9 adds
|
|
268
|
+
heading: "How it works",
|
|
269
|
+
subtitle: "Index9 adds 5 tools to your editor. Your assistant calls them when you ask about models.",
|
|
250
270
|
steps: [
|
|
251
271
|
{
|
|
252
272
|
number: "1",
|
|
253
|
-
title: "You ask
|
|
254
|
-
body: '"Pick the cheapest model that can
|
|
273
|
+
title: "You ask in chat",
|
|
274
|
+
body: '"Pick the cheapest model that can review TypeScript PRs well."'
|
|
255
275
|
},
|
|
256
276
|
{
|
|
257
277
|
number: "2",
|
|
258
278
|
title: "Your assistant calls index9",
|
|
259
|
-
body: "It
|
|
279
|
+
body: "It searches live model data, compares finalists, and runs your prompt against the top candidates."
|
|
260
280
|
},
|
|
261
281
|
{
|
|
262
282
|
number: "3",
|
|
263
|
-
title: "You get a measured
|
|
264
|
-
body: "
|
|
283
|
+
title: "You get a measured pick",
|
|
284
|
+
body: "Backed by real cost numbers and real outputs \u2014 not training-data memory."
|
|
265
285
|
}
|
|
266
286
|
]
|
|
267
287
|
},
|
|
268
288
|
caseStudy: {
|
|
269
289
|
label: "Case study",
|
|
270
|
-
heading: "A real session, not a
|
|
271
|
-
subheading: "A Claude Code session picking a TypeScript code-review model.
|
|
290
|
+
heading: "A real session, not a mockup",
|
|
291
|
+
subheading: "A Claude Code session picking a TypeScript code-review model. Real tool calls, real verdict.",
|
|
272
292
|
prompt: {
|
|
273
293
|
title: "The prompt",
|
|
274
|
-
body: "Pick
|
|
294
|
+
body: "Pick a model for a TypeScript code-review bot that runs on every PR. I want real quality without paying frontier rates on routine reviews. Test against this sample diff."
|
|
275
295
|
},
|
|
276
296
|
toolCalls: {
|
|
277
|
-
title: "
|
|
297
|
+
title: "What the assistant did",
|
|
278
298
|
subtitle: "in order",
|
|
279
299
|
calls: [
|
|
300
|
+
{ tool: "find_models", params: "newest first", note: "skip stale training picks" },
|
|
280
301
|
{
|
|
281
302
|
tool: "find_models",
|
|
282
|
-
params: "
|
|
283
|
-
note: "recent releases"
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
tool: "find_models",
|
|
287
|
-
params: 'q="code review reasoning", structured_output',
|
|
303
|
+
params: "code review + structured output",
|
|
288
304
|
note: "task fit"
|
|
289
305
|
},
|
|
290
|
-
{
|
|
291
|
-
|
|
292
|
-
params: 'q="not frontier price", maxPrice=6',
|
|
293
|
-
note: "budget filter"
|
|
294
|
-
},
|
|
295
|
-
{ tool: "get_models", params: "\xD7 12 candidates", note: "metadata lookup" },
|
|
306
|
+
{ tool: "find_models", params: "max $2/M, every-PR budget", note: "rule out frontier" },
|
|
307
|
+
{ tool: "get_models", params: "8 candidates", note: "metadata lookup" },
|
|
296
308
|
{
|
|
297
309
|
tool: "compare_models",
|
|
298
|
-
params: "
|
|
299
|
-
note: "
|
|
310
|
+
params: "4 finalists, ~3000 token PR diff",
|
|
311
|
+
note: "per-PR cost projection"
|
|
300
312
|
},
|
|
301
|
-
{ tool: "test_model", params: "
|
|
313
|
+
{ tool: "test_model", params: "dry-run \xD7 4", note: "cost estimate" },
|
|
302
314
|
{
|
|
303
315
|
tool: "test_model",
|
|
304
|
-
params: "live \xD7
|
|
305
|
-
note: "real
|
|
316
|
+
params: "live \xD7 4, JSON output",
|
|
317
|
+
note: "real bug-catch test"
|
|
306
318
|
}
|
|
307
319
|
]
|
|
308
320
|
},
|
|
309
|
-
consideredTitle: "
|
|
310
|
-
consideredSubtitle: "
|
|
321
|
+
consideredTitle: "Recent models, evaluated",
|
|
322
|
+
consideredSubtitle: "A trimmed view of the candidates the assistant ruled in and out. Each row pairs a decision with the reason behind it.",
|
|
311
323
|
consideredRows: [
|
|
312
|
-
{
|
|
313
|
-
id: "openai/gpt-5.5-pro",
|
|
314
|
-
age: "6h ago",
|
|
315
|
-
decision: "skip",
|
|
316
|
-
reason: "too expensive for every PR"
|
|
317
|
-
},
|
|
318
324
|
{
|
|
319
325
|
id: "openai/gpt-5.5",
|
|
320
|
-
age: "
|
|
326
|
+
age: "1d ago",
|
|
321
327
|
decision: "skip",
|
|
322
|
-
reason: "
|
|
328
|
+
reason: "~$0.027 per PR, 5\xD7 the pick for the same outcome"
|
|
323
329
|
},
|
|
324
330
|
{
|
|
325
|
-
id: "
|
|
326
|
-
age: "
|
|
331
|
+
id: "xiaomi/mimo-v2.5-pro",
|
|
332
|
+
age: "3d ago",
|
|
327
333
|
decision: "tested",
|
|
328
|
-
reason: "
|
|
334
|
+
reason: "good structure, missed the precision edge case"
|
|
329
335
|
},
|
|
330
336
|
{
|
|
331
337
|
id: "deepseek/deepseek-v4-flash",
|
|
332
|
-
age: "14h ago",
|
|
333
|
-
decision: "skip",
|
|
334
|
-
reason: "cheaper sibling, lower quality expected"
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
id: "xiaomi/mimo-v2.5-pro",
|
|
338
|
-
age: "2d ago",
|
|
339
|
-
decision: "shortlisted",
|
|
340
|
-
reason: "recent + reasoning + structured output"
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
id: "inclusionai/ling-2.6-1t:free",
|
|
344
338
|
age: "1d ago",
|
|
345
|
-
decision: "
|
|
346
|
-
reason: "
|
|
339
|
+
decision: "tested",
|
|
340
|
+
reason: "7\xD7 cheaper than the pick, but missed both bugs"
|
|
347
341
|
},
|
|
348
342
|
{
|
|
349
|
-
id: "
|
|
350
|
-
age: "
|
|
351
|
-
decision: "
|
|
352
|
-
reason: "
|
|
343
|
+
id: "z-ai/glm-5.1",
|
|
344
|
+
age: "2w ago",
|
|
345
|
+
decision: "shortlisted",
|
|
346
|
+
reason: "caught both bugs at ~$0.005 per PR"
|
|
353
347
|
}
|
|
354
348
|
],
|
|
355
349
|
verdict: {
|
|
356
|
-
title: "The
|
|
357
|
-
model: "
|
|
358
|
-
body: "
|
|
350
|
+
title: "The pick",
|
|
351
|
+
model: "z-ai/glm-5.1",
|
|
352
|
+
body: "Open-weight, $1.05 per million input tokens. Caught both bugs in the sample diff at roughly $0.005 per PR, about 5\xD7 cheaper than running gpt-5.5 on every commit."
|
|
359
353
|
},
|
|
360
354
|
quote: {
|
|
361
|
-
body: "The
|
|
362
|
-
attribution: "index9 session trace
|
|
355
|
+
body: "The frontier model would have caught both bugs, at 5\xD7 the cost. The cheapest candidate missed them entirely. Only the live test surfaced the model that did both.",
|
|
356
|
+
attribution: "index9 session trace"
|
|
363
357
|
}
|
|
364
358
|
},
|
|
365
359
|
toolsSection: {
|
|
366
360
|
label: "Tools",
|
|
367
|
-
heading: "
|
|
368
|
-
subheading: "
|
|
369
|
-
openRouterKey: "OpenRouter API key
|
|
370
|
-
noKeyRequired: "No
|
|
361
|
+
heading: "The 5 tools",
|
|
362
|
+
subheading: "Your assistant chains these together. You don't call them directly.",
|
|
363
|
+
openRouterKey: "OpenRouter API key",
|
|
364
|
+
noKeyRequired: "No key required",
|
|
371
365
|
requiresLabel: "Requires ",
|
|
372
366
|
cards: [
|
|
373
367
|
{
|
|
374
368
|
name: "list_facets",
|
|
375
|
-
action: "
|
|
369
|
+
action: "list_facets",
|
|
376
370
|
displayName: "list_facets",
|
|
377
371
|
fullName: null,
|
|
378
|
-
description: "
|
|
372
|
+
description: "Lists available providers, capabilities, and modalities to filter by.",
|
|
379
373
|
badge: null,
|
|
380
374
|
requiresKey: false
|
|
381
375
|
},
|
|
382
376
|
{
|
|
383
377
|
name: "find_models",
|
|
384
|
-
action: "
|
|
378
|
+
action: "find_models",
|
|
385
379
|
displayName: "find_models",
|
|
386
380
|
fullName: null,
|
|
387
|
-
description: `
|
|
381
|
+
description: `Searches ${MODEL_COUNT} models by price, context size, capabilities, or natural language.`,
|
|
388
382
|
badge: null,
|
|
389
383
|
requiresKey: false
|
|
390
384
|
},
|
|
391
385
|
{
|
|
392
386
|
name: "get_models",
|
|
393
|
-
action: "
|
|
387
|
+
action: "get_models",
|
|
394
388
|
displayName: "get_models",
|
|
395
389
|
fullName: null,
|
|
396
|
-
description: "
|
|
390
|
+
description: "Pulls full specs and current pricing for any model.",
|
|
397
391
|
badge: null,
|
|
398
392
|
requiresKey: false
|
|
399
393
|
},
|
|
400
394
|
{
|
|
401
395
|
name: "compare_models",
|
|
402
|
-
action: "
|
|
396
|
+
action: "compare_models",
|
|
403
397
|
displayName: "compare_models",
|
|
404
398
|
fullName: null,
|
|
405
|
-
description: "
|
|
399
|
+
description: "Diffs 2\u201310 finalists side-by-side. Flags the cheapest pick for your expected token mix.",
|
|
406
400
|
badge: null,
|
|
407
401
|
requiresKey: false
|
|
408
402
|
},
|
|
409
403
|
{
|
|
410
404
|
name: "test_model",
|
|
411
|
-
action: "
|
|
405
|
+
action: "test_model",
|
|
412
406
|
displayName: "test_model",
|
|
413
407
|
fullName: null,
|
|
414
|
-
description: "
|
|
415
|
-
badge: "Live
|
|
408
|
+
description: "Runs your prompt across models. Returns output, latency, and real cost. Or dry-run for cost only.",
|
|
409
|
+
badge: "Live",
|
|
416
410
|
requiresKey: true
|
|
417
411
|
}
|
|
418
412
|
]
|
|
@@ -423,7 +417,7 @@ var SITE = {
|
|
|
423
417
|
items: [
|
|
424
418
|
{
|
|
425
419
|
question: "What is MCP?",
|
|
426
|
-
answer: "
|
|
420
|
+
answer: "A protocol that lets AI assistants call external tools. Index9 is one of those tools.",
|
|
427
421
|
link: {
|
|
428
422
|
label: "Learn more about MCP",
|
|
429
423
|
url: "https://modelcontextprotocol.io"
|
|
@@ -431,45 +425,40 @@ var SITE = {
|
|
|
431
425
|
},
|
|
432
426
|
{
|
|
433
427
|
question: "Who is index9 for?",
|
|
434
|
-
answer: "Developers using
|
|
428
|
+
answer: "Developers using Claude Code, Cursor, VS Code, or Codex who want their assistant to pick models from current data instead of training-data memory.",
|
|
435
429
|
link: null
|
|
436
430
|
},
|
|
437
431
|
{
|
|
438
|
-
question: "
|
|
439
|
-
answer:
|
|
432
|
+
question: "Does it pick the model for me?",
|
|
433
|
+
answer: "No \u2014 it gives your assistant the data (search results, specs, cost diffs, live test outputs). Your assistant makes the call.",
|
|
440
434
|
link: null
|
|
441
435
|
},
|
|
442
436
|
{
|
|
443
|
-
question: "
|
|
444
|
-
answer:
|
|
437
|
+
question: "How does live testing work?",
|
|
438
|
+
answer: `test_model sends your prompt to up to ${LIMITS.testModelsMax} models via OpenRouter and returns output, latency, tokens, and cost. Dry-run mode estimates cost without running inference.`,
|
|
445
439
|
link: null
|
|
446
440
|
},
|
|
447
441
|
{
|
|
448
|
-
question: "
|
|
449
|
-
answer:
|
|
442
|
+
question: "Which models?",
|
|
443
|
+
answer: `${MODEL_COUNT} from OpenRouter \u2014 OpenAI, Anthropic, Google, Meta, Mistral, DeepSeek, and more. Metadata refreshes every 30 minutes.`,
|
|
450
444
|
link: null
|
|
451
445
|
},
|
|
452
446
|
{
|
|
453
|
-
question: "
|
|
454
|
-
answer:
|
|
447
|
+
question: "Do you store my prompts or keys?",
|
|
448
|
+
answer: "No. Index9 doesn't store prompts, outputs, or API keys. Live tests are proxied straight to OpenRouter.",
|
|
455
449
|
link: null
|
|
456
450
|
},
|
|
457
451
|
{
|
|
458
452
|
question: "What's the project status?",
|
|
459
|
-
answer: "
|
|
460
|
-
link: null
|
|
461
|
-
},
|
|
462
|
-
{
|
|
463
|
-
question: "Is my data stored?",
|
|
464
|
-
answer: "No. index9 does not store prompts, outputs, or API keys. Live tests are proxied to OpenRouter.",
|
|
453
|
+
answer: "Stable and in active use. Issues and feature requests welcome on GitHub.",
|
|
465
454
|
link: null
|
|
466
455
|
}
|
|
467
456
|
]
|
|
468
457
|
},
|
|
469
458
|
install: {
|
|
470
459
|
label: "Setup",
|
|
471
|
-
heading: "
|
|
472
|
-
subheading: "
|
|
460
|
+
heading: "Install",
|
|
461
|
+
subheading: "Pick your editor and paste the config.",
|
|
473
462
|
configs: [
|
|
474
463
|
{
|
|
475
464
|
id: "cursor",
|
|
@@ -501,14 +490,14 @@ var SITE = {
|
|
|
501
490
|
{
|
|
502
491
|
id: "claude-code",
|
|
503
492
|
label: "Claude Code",
|
|
504
|
-
paths: [
|
|
493
|
+
paths: [],
|
|
505
494
|
config: `claude mcp add --transport stdio index9 -- npx -y @index9/mcp@latest`,
|
|
506
495
|
copyHint: "# Run in terminal (adds to ~/.claude.json)"
|
|
507
496
|
},
|
|
508
497
|
{
|
|
509
498
|
id: "codex",
|
|
510
499
|
label: "Codex",
|
|
511
|
-
paths: [
|
|
500
|
+
paths: [],
|
|
512
501
|
config: `codex mcp add index9 -- npx -y @index9/mcp@latest`,
|
|
513
502
|
copyHint: "# Run in terminal (adds to ~/.codex/config.toml)"
|
|
514
503
|
}
|
|
@@ -516,8 +505,9 @@ var SITE = {
|
|
|
516
505
|
copyButton: "Copy",
|
|
517
506
|
copiedButton: "Copied",
|
|
518
507
|
copyAriaLabel: "Copy configuration",
|
|
519
|
-
|
|
520
|
-
|
|
508
|
+
copiedAnnouncement: "Configuration copied to clipboard",
|
|
509
|
+
setupNote: "Want live tests?",
|
|
510
|
+
setupLink: "Add an OpenRouter key",
|
|
521
511
|
setupUrl: "https://github.com/index9-org/mcp#openrouter-api-key"
|
|
522
512
|
},
|
|
523
513
|
footer: {
|
|
@@ -577,7 +567,8 @@ var SearchQuerySchema = z2.object({
|
|
|
577
567
|
capabilitiesAny: z2.array(z2.enum(CAPABILITIES)).optional(),
|
|
578
568
|
modality: z2.enum(OUTPUT_MODALITIES).optional(),
|
|
579
569
|
provider: z2.array(z2.string().min(1)).optional(),
|
|
580
|
-
excludeFree: z2.boolean().optional()
|
|
570
|
+
excludeFree: z2.boolean().optional(),
|
|
571
|
+
requireKeywordMatch: z2.boolean().optional()
|
|
581
572
|
}).strict();
|
|
582
573
|
var SearchResultSchema = z2.object({
|
|
583
574
|
id: z2.string(),
|
|
@@ -589,7 +580,11 @@ var SearchResultSchema = z2.object({
|
|
|
589
580
|
maxOutputTokens: z2.number().nullable(),
|
|
590
581
|
pricing: z2.object({
|
|
591
582
|
promptPerMillion: z2.number().nullable(),
|
|
592
|
-
completionPerMillion: z2.number().nullable()
|
|
583
|
+
completionPerMillion: z2.number().nullable(),
|
|
584
|
+
promptPerToken: z2.number().nullable().optional(),
|
|
585
|
+
completionPerToken: z2.number().nullable().optional(),
|
|
586
|
+
requestUsd: z2.number().nullable().optional(),
|
|
587
|
+
effectivePromptPerMillion: z2.number().nullable().optional()
|
|
593
588
|
}),
|
|
594
589
|
inputModalities: z2.array(z2.string()),
|
|
595
590
|
outputModalities: z2.array(z2.string()),
|
|
@@ -603,13 +598,15 @@ var SearchResponseSchema = z2.object({
|
|
|
603
598
|
limit: z2.number(),
|
|
604
599
|
hasMore: z2.boolean(),
|
|
605
600
|
sortBy: SearchSortBySchema,
|
|
606
|
-
sortOrder: SearchSortOrderSchema
|
|
601
|
+
sortOrder: SearchSortOrderSchema,
|
|
602
|
+
priceSortBasis: z2.literal("effective_prompt_per_million").optional()
|
|
607
603
|
}),
|
|
608
604
|
meta: z2.object({
|
|
609
605
|
queryMode: z2.enum(["semantic", "filter_only"]),
|
|
610
606
|
ranking: z2.literal("hybrid_rrf"),
|
|
611
607
|
confidence: z2.enum(["high", "low"]).optional(),
|
|
612
|
-
suggestion: z2.string().optional()
|
|
608
|
+
suggestion: z2.string().optional(),
|
|
609
|
+
lowConfidenceReason: z2.enum(["no_keyword_matches", "no_results"]).optional()
|
|
613
610
|
})
|
|
614
611
|
});
|
|
615
612
|
var FindModelsToolResultSchema = SearchResponseSchema.extend({
|
|
@@ -625,6 +622,8 @@ var BatchModelLookupRequestSchema = z3.object({
|
|
|
625
622
|
var ModelPricingSchema = z3.object({
|
|
626
623
|
promptPerMillion: z3.number().nullable(),
|
|
627
624
|
completionPerMillion: z3.number().nullable(),
|
|
625
|
+
promptPerToken: z3.number().nullable().optional(),
|
|
626
|
+
completionPerToken: z3.number().nullable().optional(),
|
|
628
627
|
requestUsd: z3.number().nullable(),
|
|
629
628
|
imageUsd: z3.number().nullable()
|
|
630
629
|
});
|
|
@@ -655,7 +654,8 @@ var BatchModelLookupResponseSchema = z3.object({
|
|
|
655
654
|
missingIds: z3.array(z3.string()),
|
|
656
655
|
resolvedAliases: z3.record(z3.string(), z3.string()).optional(),
|
|
657
656
|
ambiguousAliases: z3.record(z3.string(), z3.array(z3.string())).optional(),
|
|
658
|
-
suggestions: z3.record(z3.string(), z3.array(z3.string())).optional()
|
|
657
|
+
suggestions: z3.record(z3.string(), z3.array(z3.string())).optional(),
|
|
658
|
+
missingDiagnostics: z3.record(z3.string(), MissingModelDiagnosticSchema).optional()
|
|
659
659
|
}).strict();
|
|
660
660
|
var GetModelsToolResultSchema = z3.object({
|
|
661
661
|
results: z3.array(ModelResponseSchema.nullable()),
|
|
@@ -663,11 +663,13 @@ var GetModelsToolResultSchema = z3.object({
|
|
|
663
663
|
resolvedAliases: z3.record(z3.string(), z3.string()).optional(),
|
|
664
664
|
ambiguousAliases: z3.record(z3.string(), z3.array(z3.string())).optional(),
|
|
665
665
|
suggestions: z3.record(z3.string(), z3.array(z3.string())).optional(),
|
|
666
|
+
missingDiagnostics: z3.record(z3.string(), MissingModelDiagnosticSchema).optional(),
|
|
666
667
|
_index9: Index9MetaSchema
|
|
667
668
|
});
|
|
668
669
|
|
|
669
670
|
// ../core/dist/schemas/compare.js
|
|
670
671
|
import { z as z4 } from "zod";
|
|
672
|
+
var PricingBasisSchema = z4.enum(["exact_per_token", "rounded_per_million", "unavailable"]);
|
|
671
673
|
var CompareRequestSchema = z4.object({
|
|
672
674
|
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`),
|
|
673
675
|
expectedPromptTokens: z4.number().int().positive().optional(),
|
|
@@ -704,7 +706,10 @@ var CompareWorkloadCostSchema = z4.object({
|
|
|
704
706
|
modelId: z4.string(),
|
|
705
707
|
promptTokens: z4.number().int().nonnegative(),
|
|
706
708
|
completionTokens: z4.number().int().nonnegative(),
|
|
707
|
-
totalCostUsd: z4.number().nullable()
|
|
709
|
+
totalCostUsd: z4.number().nullable(),
|
|
710
|
+
tokenCostUsd: z4.number().nullable().optional(),
|
|
711
|
+
requestCostUsd: z4.number().nullable().optional(),
|
|
712
|
+
pricingBasis: PricingBasisSchema.optional()
|
|
708
713
|
});
|
|
709
714
|
var CompareResponseSchema = z4.object({
|
|
710
715
|
models: z4.array(ModelResponseSchema),
|
|
@@ -716,7 +721,8 @@ var CompareResponseSchema = z4.object({
|
|
|
716
721
|
resolvedAliases: z4.record(z4.string(), z4.string()).optional(),
|
|
717
722
|
missingIds: z4.array(z4.string()),
|
|
718
723
|
suggestions: z4.record(z4.string(), z4.array(z4.string())).optional(),
|
|
719
|
-
ambiguousAliases: z4.record(z4.string(), z4.array(z4.string())).optional()
|
|
724
|
+
ambiguousAliases: z4.record(z4.string(), z4.array(z4.string())).optional(),
|
|
725
|
+
missingDiagnostics: z4.record(z4.string(), MissingModelDiagnosticSchema).optional()
|
|
720
726
|
}).strict();
|
|
721
727
|
var CompareModelsToolResultSchema = CompareResponseSchema.extend({
|
|
722
728
|
_index9: Index9MetaSchema
|
|
@@ -791,8 +797,17 @@ var TestPricingUsedSchema = z6.object({
|
|
|
791
797
|
promptPerToken: z6.number().nullable().optional(),
|
|
792
798
|
completionPerToken: z6.number().nullable().optional(),
|
|
793
799
|
promptPerMillion: z6.number().nullable().optional(),
|
|
794
|
-
completionPerMillion: z6.number().nullable().optional()
|
|
800
|
+
completionPerMillion: z6.number().nullable().optional(),
|
|
801
|
+
requestUsd: z6.number().nullable().optional()
|
|
795
802
|
});
|
|
803
|
+
var TestFailureReasonSchema = z6.enum([
|
|
804
|
+
"insufficient_credits",
|
|
805
|
+
"model_unavailable",
|
|
806
|
+
"rate_limited",
|
|
807
|
+
"timeout",
|
|
808
|
+
"invalid_request",
|
|
809
|
+
"unknown"
|
|
810
|
+
]);
|
|
796
811
|
var TestModelMetadataSchema = z6.object({
|
|
797
812
|
id: z6.string(),
|
|
798
813
|
name: z6.string(),
|
|
@@ -817,6 +832,7 @@ var TestResultFailureSchema = z6.object({
|
|
|
817
832
|
ok: z6.literal(false),
|
|
818
833
|
model: TestModelMetadataSchema,
|
|
819
834
|
error: z6.string(),
|
|
835
|
+
failureReason: TestFailureReasonSchema.optional(),
|
|
820
836
|
latencyMs: z6.number().min(0)
|
|
821
837
|
});
|
|
822
838
|
var TestResultSchema = z6.discriminatedUnion("ok", [
|
|
@@ -828,7 +844,11 @@ var TestEstimateResultSchema = z6.object({
|
|
|
828
844
|
resolvedModelId: z6.string().optional(),
|
|
829
845
|
model: TestModelMetadataSchema,
|
|
830
846
|
tokens: UsageTokensSchema,
|
|
831
|
-
estimatedCost: z6.number().nullable().optional()
|
|
847
|
+
estimatedCost: z6.number().nullable().optional(),
|
|
848
|
+
tokenCostUsd: z6.number().nullable().optional(),
|
|
849
|
+
requestCostUsd: z6.number().nullable().optional(),
|
|
850
|
+
totalCostUsd: z6.number().nullable().optional(),
|
|
851
|
+
estimatedCostBasis: PricingBasisSchema.optional()
|
|
832
852
|
});
|
|
833
853
|
var TestDryRunResponseSchema = z6.object({
|
|
834
854
|
dryRun: z6.literal(true),
|
|
@@ -1026,6 +1046,7 @@ async function handleSearchModels(ctx, args) {
|
|
|
1026
1046
|
if (q.modality) params.modality = q.modality;
|
|
1027
1047
|
if (q.provider?.length) params.provider = q.provider.join(",");
|
|
1028
1048
|
if (q.excludeFree === true) params.excludeFree = "true";
|
|
1049
|
+
if (q.requireKeywordMatch === true) params.requireKeywordMatch = "true";
|
|
1029
1050
|
return callApi(
|
|
1030
1051
|
ctx,
|
|
1031
1052
|
buildUrl(ctx.baseUrl, API_PATHS.search, params),
|
|
@@ -1117,7 +1138,8 @@ async function createServer() {
|
|
|
1117
1138
|
capabilitiesAny: z7.array(z7.enum(CAPABILITIES)).optional().describe(PARAM_DESCRIPTIONS.capabilitiesAny),
|
|
1118
1139
|
modality: z7.enum(OUTPUT_MODALITIES).optional().describe(PARAM_DESCRIPTIONS.modality),
|
|
1119
1140
|
provider: z7.array(z7.string().min(1)).optional().describe(PARAM_DESCRIPTIONS.provider),
|
|
1120
|
-
excludeFree: z7.boolean().optional().describe(PARAM_DESCRIPTIONS.excludeFree)
|
|
1141
|
+
excludeFree: z7.boolean().optional().describe(PARAM_DESCRIPTIONS.excludeFree),
|
|
1142
|
+
requireKeywordMatch: z7.boolean().optional().describe(PARAM_DESCRIPTIONS.requireKeywordMatch)
|
|
1121
1143
|
},
|
|
1122
1144
|
outputSchema: FindModelsToolResultSchema.shape,
|
|
1123
1145
|
annotations: { readOnlyHint: true }
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@index9/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
24
|
-
"zod": "^4.3
|
|
24
|
+
"zod": "^4.4.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/node": "^25.6.
|
|
27
|
+
"@types/node": "^25.6.1",
|
|
28
28
|
"tsup": "^8.5.1",
|
|
29
29
|
"typescript": "6.0.3",
|
|
30
30
|
"vitest": "^4.1.5",
|
|
31
|
-
"@index9/core": "2.
|
|
31
|
+
"@index9/core": "2.4.0"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=20"
|