@index9/mcp 6.1.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 +60 -14
- package/manifest.json +1 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -48,6 +48,11 @@ var MissingModelDiagnosticSchema = z.object({
|
|
|
48
48
|
provider: z.string().optional(),
|
|
49
49
|
message: z.string()
|
|
50
50
|
});
|
|
51
|
+
var SuggestionEntrySchema = z.object({
|
|
52
|
+
id: z.string(),
|
|
53
|
+
name: z.string(),
|
|
54
|
+
created: z.number().nullable()
|
|
55
|
+
});
|
|
51
56
|
var UserContentTextPartSchema = z.strictObject({
|
|
52
57
|
type: z.literal("text"),
|
|
53
58
|
text: z.string().trim().min(1)
|
|
@@ -108,8 +113,9 @@ Key rules:
|
|
|
108
113
|
- find_models price-asc tends to be dominated by free preview models \u2014 pass \`excludeFree=true\` when you want a paid SLA.
|
|
109
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.
|
|
110
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.
|
|
111
|
-
- get_models
|
|
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.
|
|
112
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.
|
|
113
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.
|
|
114
120
|
- test_model with \`dryRun=false\` (default) requires OPENROUTER_API_KEY and incurs real usage costs.
|
|
115
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.
|
|
@@ -152,7 +158,7 @@ Pass result.id to get_models for full specs or to test_model for live testing.`,
|
|
|
152
158
|
|
|
153
159
|
Call after find_models to inspect candidates, or directly when the user names a model (format: 'provider/model-name').
|
|
154
160
|
|
|
155
|
-
Response: { results: (Model | null)[], missingIds: string[], resolvedAliases?: Record<alias, canonicalId>, ambiguousAliases?: Record<alias, candidateIds[]>, suggestions?: Record<unknownId,
|
|
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:
|
|
156
162
|
- id, canonicalSlug, name, description
|
|
157
163
|
- created (unix seconds), createdAt (ISO 8601), knowledgeCutoff (ISO date or null)
|
|
158
164
|
- contextLength (tokens), maxOutputTokens, isModerated
|
|
@@ -161,7 +167,7 @@ Response: { results: (Model | null)[], missingIds: string[], resolvedAliases?: R
|
|
|
161
167
|
- capabilities[]: normalized capability flags (same values as find_models and capabilitiesAll/Any)
|
|
162
168
|
- supportedParameters[]: OpenRouter parameters the model accepts (e.g., "temperature", "tools", "response_format")
|
|
163
169
|
|
|
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
|
|
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).
|
|
165
171
|
|
|
166
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\`.`,
|
|
167
173
|
requiresKey: false
|
|
@@ -174,13 +180,13 @@ Entries in results are null when the id is unknown; those ids appear in missingI
|
|
|
174
180
|
|
|
175
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.
|
|
176
182
|
|
|
177
|
-
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? }.
|
|
178
184
|
|
|
179
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.
|
|
180
186
|
|
|
181
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.
|
|
182
188
|
|
|
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).`,
|
|
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.`,
|
|
184
190
|
requiresKey: false
|
|
185
191
|
},
|
|
186
192
|
list_facets: {
|
|
@@ -216,7 +222,9 @@ Parameters:
|
|
|
216
222
|
|
|
217
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.
|
|
218
224
|
|
|
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
|
|
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\`.`,
|
|
220
228
|
requiresKey: true
|
|
221
229
|
}
|
|
222
230
|
};
|
|
@@ -654,7 +662,7 @@ var BatchModelLookupResponseSchema = z3.object({
|
|
|
654
662
|
missingIds: z3.array(z3.string()),
|
|
655
663
|
resolvedAliases: z3.record(z3.string(), z3.string()).optional(),
|
|
656
664
|
ambiguousAliases: z3.record(z3.string(), z3.array(z3.string())).optional(),
|
|
657
|
-
suggestions: z3.record(z3.string(), z3.array(
|
|
665
|
+
suggestions: z3.record(z3.string(), z3.array(SuggestionEntrySchema)).optional(),
|
|
658
666
|
missingDiagnostics: z3.record(z3.string(), MissingModelDiagnosticSchema).optional()
|
|
659
667
|
}).strict();
|
|
660
668
|
var GetModelsToolResultSchema = z3.object({
|
|
@@ -662,7 +670,7 @@ var GetModelsToolResultSchema = z3.object({
|
|
|
662
670
|
missingIds: z3.array(z3.string()),
|
|
663
671
|
resolvedAliases: z3.record(z3.string(), z3.string()).optional(),
|
|
664
672
|
ambiguousAliases: z3.record(z3.string(), z3.array(z3.string())).optional(),
|
|
665
|
-
suggestions: z3.record(z3.string(), z3.array(
|
|
673
|
+
suggestions: z3.record(z3.string(), z3.array(SuggestionEntrySchema)).optional(),
|
|
666
674
|
missingDiagnostics: z3.record(z3.string(), MissingModelDiagnosticSchema).optional(),
|
|
667
675
|
_index9: Index9MetaSchema
|
|
668
676
|
});
|
|
@@ -720,7 +728,7 @@ var CompareResponseSchema = z4.object({
|
|
|
720
728
|
workloadCosts: z4.array(CompareWorkloadCostSchema).optional(),
|
|
721
729
|
resolvedAliases: z4.record(z4.string(), z4.string()).optional(),
|
|
722
730
|
missingIds: z4.array(z4.string()),
|
|
723
|
-
suggestions: z4.record(z4.string(), z4.array(
|
|
731
|
+
suggestions: z4.record(z4.string(), z4.array(SuggestionEntrySchema)).optional(),
|
|
724
732
|
ambiguousAliases: z4.record(z4.string(), z4.array(z4.string())).optional(),
|
|
725
733
|
missingDiagnostics: z4.record(z4.string(), MissingModelDiagnosticSchema).optional()
|
|
726
734
|
}).strict();
|
|
@@ -850,13 +858,22 @@ var TestEstimateResultSchema = z6.object({
|
|
|
850
858
|
totalCostUsd: z6.number().nullable().optional(),
|
|
851
859
|
estimatedCostBasis: PricingBasisSchema.optional()
|
|
852
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
|
+
};
|
|
853
868
|
var TestDryRunResponseSchema = z6.object({
|
|
854
869
|
dryRun: z6.literal(true),
|
|
855
870
|
results: z6.array(TestEstimateResultSchema),
|
|
856
|
-
disclaimer: z6.string()
|
|
871
|
+
disclaimer: z6.string(),
|
|
872
|
+
...TestResolutionFieldsSchema
|
|
857
873
|
});
|
|
858
874
|
var TestLiveResponseSchema = z6.object({
|
|
859
|
-
results: z6.array(TestResultSchema)
|
|
875
|
+
results: z6.array(TestResultSchema),
|
|
876
|
+
...TestResolutionFieldsSchema
|
|
860
877
|
});
|
|
861
878
|
var TestResponseSchema = z6.union([TestDryRunResponseSchema, TestLiveResponseSchema]);
|
|
862
879
|
|
|
@@ -1004,6 +1021,22 @@ function extractError(body) {
|
|
|
1004
1021
|
}
|
|
1005
1022
|
return "Request failed";
|
|
1006
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
|
+
}
|
|
1007
1040
|
async function callApi(ctx, url, options, responseSchema) {
|
|
1008
1041
|
const res = await fetchWithRetry(url, options);
|
|
1009
1042
|
let body;
|
|
@@ -1014,7 +1047,12 @@ async function callApi(ctx, url, options, responseSchema) {
|
|
|
1014
1047
|
}
|
|
1015
1048
|
if (!res.ok) {
|
|
1016
1049
|
return toResponse(
|
|
1017
|
-
{
|
|
1050
|
+
{
|
|
1051
|
+
error: extractError(body),
|
|
1052
|
+
status: res.status,
|
|
1053
|
+
...extractRecoveryFields(body),
|
|
1054
|
+
_index9: buildMeta(ctx, res.headers)
|
|
1055
|
+
},
|
|
1018
1056
|
true
|
|
1019
1057
|
);
|
|
1020
1058
|
}
|
|
@@ -1062,7 +1100,11 @@ async function handleGetModels(ctx, args) {
|
|
|
1062
1100
|
return callApi(
|
|
1063
1101
|
ctx,
|
|
1064
1102
|
`${ctx.baseUrl}${API_PATHS.model}`,
|
|
1065
|
-
{
|
|
1103
|
+
{
|
|
1104
|
+
method: "POST",
|
|
1105
|
+
headers: baseHeaders(ctx),
|
|
1106
|
+
body: JSON.stringify(parsed.data)
|
|
1107
|
+
},
|
|
1066
1108
|
BatchModelLookupResponseSchema
|
|
1067
1109
|
);
|
|
1068
1110
|
}
|
|
@@ -1074,7 +1116,11 @@ async function handleCompareModels(ctx, args) {
|
|
|
1074
1116
|
return callApi(
|
|
1075
1117
|
ctx,
|
|
1076
1118
|
`${ctx.baseUrl}${API_PATHS.compare}`,
|
|
1077
|
-
{
|
|
1119
|
+
{
|
|
1120
|
+
method: "POST",
|
|
1121
|
+
headers: baseHeaders(ctx),
|
|
1122
|
+
body: JSON.stringify(parsed.data)
|
|
1123
|
+
},
|
|
1078
1124
|
CompareResponseSchema
|
|
1079
1125
|
);
|
|
1080
1126
|
}
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@index9/mcp",
|
|
3
|
-
"version": "6.
|
|
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.
|
|
27
|
+
"@types/node": "^25.6.2",
|
|
28
28
|
"tsup": "^8.5.1",
|
|
29
29
|
"typescript": "6.0.3",
|
|
30
|
-
"vitest": "^4.1.
|
|
31
|
-
"@index9/core": "2.
|
|
30
|
+
"vitest": "^4.1.6",
|
|
31
|
+
"@index9/core": "2.5.0"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=20"
|