diff --git a/apps/server/src/mcp/OrchestratorMcpService.test.ts b/apps/server/src/mcp/OrchestratorMcpService.test.ts index 40b5584e95ac..1be59bc41184 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.test.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.test.ts @@ -2512,6 +2512,10 @@ describe("OrchestratorMcpService provider resolution", () => { { slug: "claude-opus-4-1", isLegacy: true }, ...Array.from({ length: 9 }, (_, index) => ({ slug: `claude-opus-5-${index + 1}` })), ].map((model) => ({ ...model, name: model.slug, isCustom: false, capabilities: null })), + version: "2.1.302", + updateRequiredModels: [ + { slug: "claude-fable-5-2", name: "Claude Fable 5.2", minVersion: "2.1.321" }, + ], }, ]), adapterRegistryLayer([codexInstanceId]), @@ -2549,6 +2553,11 @@ describe("OrchestratorMcpService provider resolution", () => { "claude-opus-5-6, claude-opus-5-7, claude-opus-5-8 and 2 more; " + "orchestrator_capabilities with search finds them.", ); + + const updateRequired = yield* refuse("claude-fable-5-2"); + assert.equal(updateRequired.code, "model_unavailable"); + assert.include(updateRequired.message, "v2.1.302"); + assert.include(updateRequired.message, "2.1.321"); }).pipe(Effect.provide(layerOrchestratorService.pipe(Layer.provide(layerDependencies)))); }), ); diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index cf5efb0ab333..19a413213633 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -395,16 +395,26 @@ const advertisedModel = (provider: ServerProvider, model: string) => candidate.aliases?.some((alias) => alias.toLowerCase() === model.toLowerCase()), ); +const withV = (version: string) => (version.startsWith("v") ? version : `v${version}`); + /** A provider that lists no models accepts any slug. */ -export const requireAdvertisedModel = (provider: ServerProvider, model: string) => - provider.models.length > 0 && advertisedModel(provider, model) === undefined - ? Effect.fail( - failure( - "model_unavailable", - `Model ${model} is not advertised by provider ${provider.instanceId}. ${advertisedModelsHint(provider.models, model)}`, - ), - ) - : Effect.void; +export const requireAdvertisedModel = (provider: ServerProvider, model: string) => { + if (provider.models.length === 0 || advertisedModel(provider, model) !== undefined) { + return Effect.void; + } + const updateRequired = provider.updateRequiredModels?.find( + (candidate) => candidate.slug === model, + ); + const detectedVersion = provider.installedVersion ?? provider.version; + return Effect.fail( + failure( + "model_unavailable", + updateRequired === undefined + ? `Model ${model} is not advertised by provider ${provider.instanceId}. ${advertisedModelsHint(provider.models, model)}` + : `Model ${model} needs provider ${provider.instanceId} at ${withV(updateRequired.minVersion)} or newer, but the detected CLI is ${detectedVersion === null ? "an unknown version" : withV(detectedVersion)}. Ask the user to update it or point the provider's binary path at a newer install.`, + ), + ); +}; function providerConstraints( provider: ServerProvider | undefined,