From 981af903eab077de6f05c48079eb99082b94245c Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:25:21 +0000 Subject: [PATCH 1/6] feat(mcp): route hosted AI through OpenRouter with gpt-5.5 Move the Cloudflare chat playground off the hardcoded OpenAI gpt-4o path onto the shared OpenRouter provider, and bump the OpenRouter default model to openai/gpt-5.5 so hosted agents and chat use the same modern model. Co-Authored-By: David Cramer --- docs/operations/embedded-agents.md | 6 +++--- packages/mcp-cloudflare/package.json | 1 - .../mcp-cloudflare/src/server/lib/mcp-handler.test.ts | 4 +++- packages/mcp-cloudflare/src/server/routes/chat.ts | 11 ++++++----- .../mcp-cloudflare/src/server/sentry.config.test.ts | 4 +++- packages/mcp-cloudflare/src/server/types.ts | 6 +++++- packages/mcp-cloudflare/worker-configuration.d.ts | 7 +++++-- packages/mcp-cloudflare/wrangler.test.jsonc | 4 +++- packages/mcp-core/README.md | 2 +- .../src/internal/agents/openrouter-provider.test.ts | 2 +- .../src/internal/agents/openrouter-provider.ts | 2 +- packages/mcp-server/src/cli/usage.ts | 2 +- packages/mcp-test-client/README.md | 2 +- packages/mcp-test-client/src/agent.test.ts | 6 +++--- packages/mcp-test-client/src/constants.ts | 2 +- pnpm-lock.yaml | 3 --- 16 files changed, 37 insertions(+), 27 deletions(-) diff --git a/docs/operations/embedded-agents.md b/docs/operations/embedded-agents.md index f5c4fb586..d8b82334a 100644 --- a/docs/operations/embedded-agents.md +++ b/docs/operations/embedded-agents.md @@ -72,8 +72,8 @@ For OpenRouter: ```bash export OPENROUTER_API_KEY=sk-or-... -# Optional; defaults to openai/gpt-5 -export OPENROUTER_MODEL=openai/gpt-5 +# Optional; defaults to openai/gpt-5.5 +export OPENROUTER_MODEL=openai/gpt-5.5 # Recommended, and required when multiple provider keys are set export EMBEDDED_AGENT_PROVIDER=openrouter ``` @@ -219,7 +219,7 @@ export OPENAI_MODEL=gpt-4 # Anthropic (default: claude-opus-4-5-20251101) export ANTHROPIC_MODEL=claude-sonnet-4-5-20250929 -# OpenRouter (default: openai/gpt-5) +# OpenRouter (default: openai/gpt-5.5) export OPENROUTER_MODEL=anthropic/claude-sonnet-4 ``` diff --git a/packages/mcp-cloudflare/package.json b/packages/mcp-cloudflare/package.json index 97a33fbde..ee14062a1 100644 --- a/packages/mcp-cloudflare/package.json +++ b/packages/mcp-cloudflare/package.json @@ -46,7 +46,6 @@ }, "dependencies": { "@ai-sdk/mcp": "catalog:", - "@ai-sdk/openai": "catalog:", "@ai-sdk/react": "catalog:", "@cloudflare/workers-oauth-provider": "catalog:", "@modelcontextprotocol/sdk": "catalog:", diff --git a/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts b/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts index 2ac20d854..98a0448bf 100644 --- a/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts +++ b/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts @@ -99,7 +99,9 @@ function createTestEnv(): Env { SENTRY_CLIENT_ID: "test-client-id", SENTRY_CLIENT_SECRET: "test-client-secret", SENTRY_HOST: "sentry.io", - OPENAI_API_KEY: "test-openai-key", + OPENROUTER_API_KEY: "test-openrouter-key", + OPENROUTER_MODEL: "openai/gpt-5.5", + EMBEDDED_AGENT_PROVIDER: "openrouter", OAUTH_KV: {} as KVNamespace, OAUTH_PROVIDER: { listUserGrants: vi.fn().mockResolvedValue({ items: [] }), diff --git a/packages/mcp-cloudflare/src/server/routes/chat.ts b/packages/mcp-cloudflare/src/server/routes/chat.ts index 75063eaba..69badf2cf 100644 --- a/packages/mcp-cloudflare/src/server/routes/chat.ts +++ b/packages/mcp-cloudflare/src/server/routes/chat.ts @@ -1,6 +1,6 @@ import { experimental_createMCPClient } from "@ai-sdk/mcp"; -import { openai } from "@ai-sdk/openai"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { getOpenRouterModel } from "@sentry/mcp-core/internal/agents/openrouter-provider"; import { logInfo, logIssue, logWarn } from "@sentry/mcp-core/telem/logging"; import { convertToModelMessages, @@ -93,9 +93,10 @@ async function refreshTokenIfNeeded( } export default new Hono<{ Bindings: Env }>().post("/", async (c) => { - // Validate that we have an OpenAI API key - if (!c.env.OPENAI_API_KEY) { - logIssue("OPENAI_API_KEY is not configured", { + // Validate that we have an OpenRouter API key (process.env is populated from + // worker bindings via nodejs_compat_populate_process_env). + if (!c.env.OPENROUTER_API_KEY && !process.env.OPENROUTER_API_KEY) { + logIssue("OPENROUTER_API_KEY is not configured", { loggerScope: ["cloudflare", "chat"], }); return c.json( @@ -307,7 +308,7 @@ export default new Hono<{ Bindings: Env }>().post("/", async (c) => { }); const result = streamText({ - model: openai("gpt-4o"), + model: getOpenRouterModel(), messages: modelMessages, tools, system: `You are an AI assistant designed EXCLUSIVELY for testing the Sentry MCP service. Your sole purpose is to help users test MCP functionality with their real Sentry account data - nothing more, nothing less. diff --git a/packages/mcp-cloudflare/src/server/sentry.config.test.ts b/packages/mcp-cloudflare/src/server/sentry.config.test.ts index 9d2f53aee..42590e473 100644 --- a/packages/mcp-cloudflare/src/server/sentry.config.test.ts +++ b/packages/mcp-cloudflare/src/server/sentry.config.test.ts @@ -13,7 +13,9 @@ function createEnv(overrides: Partial = {}): Env { SENTRY_CLIENT_SECRET: "test-client-secret", SENTRY_DSN: "https://public@example.ingest.sentry.io/1", SENTRY_HOST: "sentry.io", - OPENAI_API_KEY: "test-openai-key", + OPENROUTER_API_KEY: "test-openrouter-key", + OPENROUTER_MODEL: "openai/gpt-5.5", + EMBEDDED_AGENT_PROVIDER: "openrouter", OAUTH_PROVIDER: {} as Env["OAUTH_PROVIDER"], AI: {} as Ai, ...overrides, diff --git a/packages/mcp-cloudflare/src/server/types.ts b/packages/mcp-cloudflare/src/server/types.ts index 0902526ca..1c9a59751 100644 --- a/packages/mcp-cloudflare/src/server/types.ts +++ b/packages/mcp-cloudflare/src/server/types.ts @@ -60,7 +60,11 @@ export interface Env { SENTRY_ENVIRONMENT?: string; SENTRY_DSN?: string; SENTRY_HOST?: string; - OPENAI_API_KEY: string; + /** @deprecated Prefer OPENROUTER_API_KEY for hosted MCP AI features. */ + OPENAI_API_KEY?: string; + OPENROUTER_API_KEY?: string; + OPENROUTER_MODEL?: string; + EMBEDDED_AGENT_PROVIDER?: string; MCP_URL?: string; OAUTH_PROVIDER: OAuthHelpers; AI: Ai; diff --git a/packages/mcp-cloudflare/worker-configuration.d.ts b/packages/mcp-cloudflare/worker-configuration.d.ts index 867998ded..33dbf79b4 100644 --- a/packages/mcp-cloudflare/worker-configuration.d.ts +++ b/packages/mcp-cloudflare/worker-configuration.d.ts @@ -10,7 +10,10 @@ declare namespace Cloudflare { SENTRY_CLIENT_ID: string; SENTRY_CLIENT_SECRET: string; SENTRY_DSN: string; - OPENAI_API_KEY: string; + OPENAI_API_KEY?: string; + OPENROUTER_API_KEY?: string; + OPENROUTER_MODEL?: string; + EMBEDDED_AGENT_PROVIDER?: string; COOKIE_SECRET: string; CHAT_RATE_LIMITER: RateLimit; SEARCH_RATE_LIMITER: RateLimit; @@ -24,7 +27,7 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types diff --git a/packages/mcp-cloudflare/wrangler.test.jsonc b/packages/mcp-cloudflare/wrangler.test.jsonc index 3663fbfcf..ef70625fc 100644 --- a/packages/mcp-cloudflare/wrangler.test.jsonc +++ b/packages/mcp-cloudflare/wrangler.test.jsonc @@ -14,7 +14,9 @@ "SENTRY_CLIENT_ID": "test-client-id", "SENTRY_CLIENT_SECRET": "test-client-secret", "SENTRY_HOST": "sentry.io", - "OPENAI_API_KEY": "test-openai-key" + "OPENROUTER_API_KEY": "test-openrouter-key", + "OPENROUTER_MODEL": "openai/gpt-5.5", + "EMBEDDED_AGENT_PROVIDER": "openrouter" }, "kv_namespaces": [ { diff --git a/packages/mcp-core/README.md b/packages/mcp-core/README.md index 95a993eb6..100dc6a70 100644 --- a/packages/mcp-core/README.md +++ b/packages/mcp-core/README.md @@ -83,7 +83,7 @@ MCP_ADD_SCOPES=event:write # Add to default scopes (keeps defaults) OPENAI_API_KEY=your-openai-key # Use OpenAI for AI-powered search tools OPENAI_MODEL=gpt-5 # OpenAI model to use (default: "gpt-5") OPENROUTER_API_KEY=your-openrouter-key # Or use OpenRouter for AI-powered search tools -OPENROUTER_MODEL=openai/gpt-5 # OpenRouter model to use (default: "openai/gpt-5") +OPENROUTER_MODEL=openai/gpt-5.5 # OpenRouter model to use (default: "openai/gpt-5.5") OPENAI_REASONING_EFFORT=low # Reasoning effort for o1 models: "low", "medium", "high", or "" to disable (default: "low") # No environment variable exists for the OpenAI base URL override; use --openai-base-url instead. diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts index f4e601406..e179d0b59 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts @@ -74,7 +74,7 @@ describe("openrouter-provider", () => { it("uses default and configured models", () => { expect((getOpenRouterModel() as LanguageModelV3).modelId).toBe( - "openai/gpt-5", + "openai/gpt-5.5", ); process.env.OPENROUTER_MODEL = "anthropic/claude-sonnet-4"; diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.ts index 7a1241747..b987d8691 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.ts @@ -2,7 +2,7 @@ import { createOpenAI } from "@ai-sdk/openai"; import type { LanguageModel } from "ai"; import { USER_AGENT } from "../../version"; -const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5"; +const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5.5"; const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; /** diff --git a/packages/mcp-server/src/cli/usage.ts b/packages/mcp-server/src/cli/usage.ts index bd9f291f1..f7a5b4074 100644 --- a/packages/mcp-server/src/cli/usage.ts +++ b/packages/mcp-server/src/cli/usage.ts @@ -47,7 +47,7 @@ Environment variables: OPENAI_API_KEY OpenAI API key for AI-powered search tools ANTHROPIC_API_KEY Anthropic API key for AI-powered search tools OPENROUTER_API_KEY OpenRouter API key for AI-powered search tools - OPENROUTER_MODEL OpenRouter model (default: openai/gpt-5) + OPENROUTER_MODEL OpenRouter model (default: openai/gpt-5.5) EMBEDDED_AGENT_PROVIDER Provider override: openai, azure-openai, anthropic, or openrouter MCP_DISABLE_SKILLS Disable specific skills (comma-separated) diff --git a/packages/mcp-test-client/README.md b/packages/mcp-test-client/README.md index 1742e6ece..311cc4cf3 100644 --- a/packages/mcp-test-client/README.md +++ b/packages/mcp-test-client/README.md @@ -62,7 +62,7 @@ SENTRY_ACCESS_TOKEN=your_sentry_access_token # Leave unset to target the SaaS host SENTRY_HOST=sentry.example.com # Hostname only MCP_URL=https://mcp.sentry.dev # MCP server host (defaults to production) -MCP_MODEL=openai/gpt-5 # Override default model +MCP_MODEL=openai/gpt-5.5 # Override default model # Optional - Error tracking SENTRY_DSN=your_sentry_dsn # Error tracking for the client itself diff --git a/packages/mcp-test-client/src/agent.test.ts b/packages/mcp-test-client/src/agent.test.ts index b5fce1dbd..af1f89ba3 100644 --- a/packages/mcp-test-client/src/agent.test.ts +++ b/packages/mcp-test-client/src/agent.test.ts @@ -9,12 +9,12 @@ function createStreamingResponse() { start(controller) { controller.enqueue( encoder.encode( - 'data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":0,"model":"openai/gpt-5","choices":[{"index":0,"delta":{"content":"ok"},"finish_reason":null}]}\n\n', + 'data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":0,"model":"openai/gpt-5.5","choices":[{"index":0,"delta":{"content":"ok"},"finish_reason":null}]}\n\n', ), ); controller.enqueue( encoder.encode( - 'data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":0,"model":"openai/gpt-5","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', + 'data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":0,"model":"openai/gpt-5.5","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', ), ); controller.enqueue(encoder.encode("data: [DONE]\n\n")); @@ -102,7 +102,7 @@ describe("runAgent", () => { expect(requestUrl).toBe("https://openrouter.ai/api/v1/chat/completions"); expect(authorization).toBe("Bearer sk-or-test"); expect(requestBody).toMatchObject({ - model: "openai/gpt-5", + model: "openai/gpt-5.5", stream: true, }); }); diff --git a/packages/mcp-test-client/src/constants.ts b/packages/mcp-test-client/src/constants.ts index d96d28cab..b7403fa03 100644 --- a/packages/mcp-test-client/src/constants.ts +++ b/packages/mcp-test-client/src/constants.ts @@ -2,7 +2,7 @@ export const DEFAULT_MCP_URL = "https://mcp.sentry.dev"; export const DEFAULT_OPENAI_MODEL = "gpt-4o"; -export const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5"; +export const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5.5"; export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; // OAuth configuration. Overridable via MCP_OAUTH_PORT, MCP_OAUTH_HOST, and diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 598a73867..a0a055af0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -237,9 +237,6 @@ importers: '@ai-sdk/mcp': specifier: 'catalog:' version: 1.0.16(zod@4.4.3) - '@ai-sdk/openai': - specifier: 'catalog:' - version: 3.0.23(zod@4.4.3) '@ai-sdk/react': specifier: 'catalog:' version: 3.0.232(react@19.2.8)(zod@4.4.3) From 0d5df9e6df4c63132e5056c5c077cf14e6df6311 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:41:14 +0000 Subject: [PATCH 2/6] feat(mcp): default OpenRouter to gpt-5.6-luna at high effort Switch the shared OpenRouter default from gpt-5.5 to the cheaper gpt-5.6-luna tier and send reasoningEffort=high for agents and chat. Effort remains overridable via OPENROUTER_REASONING_EFFORT. Co-Authored-By: David Cramer --- README.md | 3 +- docs/operations/embedded-agents.md | 8 ++-- .../src/server/lib/mcp-handler.test.ts | 2 +- .../mcp-cloudflare/src/server/routes/chat.ts | 6 ++- .../src/server/sentry.config.test.ts | 2 +- packages/mcp-cloudflare/src/server/types.ts | 1 + .../mcp-cloudflare/worker-configuration.d.ts | 3 +- packages/mcp-cloudflare/wrangler.test.jsonc | 2 +- packages/mcp-core/README.md | 3 +- .../agents/openrouter-provider.test.ts | 44 ++++++++++++++++++- .../internal/agents/openrouter-provider.ts | 30 ++++++++++++- .../src/internal/agents/provider-factory.ts | 18 ++++---- packages/mcp-server/src/cli/usage.ts | 3 +- packages/mcp-test-client/README.md | 2 +- packages/mcp-test-client/src/agent.test.ts | 6 +-- packages/mcp-test-client/src/constants.ts | 2 +- turbo.json | 1 + 17 files changed, 107 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 2054c52d4..720edc6e5 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,8 @@ EMBEDDED_AGENT_PROVIDER= # Required when multiple provider keys are set: 'op OPENAI_API_KEY= # Required if using OpenAI ANTHROPIC_API_KEY= # Required if using Anthropic OPENROUTER_API_KEY= # Required if using OpenRouter -OPENROUTER_MODEL= # Optional OpenRouter model, defaults to 'openai/gpt-5' +OPENROUTER_MODEL= # Optional OpenRouter model, defaults to 'openai/gpt-5.6-luna' +OPENROUTER_REASONING_EFFORT= # Optional OpenRouter reasoning effort, defaults to 'high' # Optional overrides SENTRY_HOST= # For self-hosted deployments diff --git a/docs/operations/embedded-agents.md b/docs/operations/embedded-agents.md index d8b82334a..2f4f154e4 100644 --- a/docs/operations/embedded-agents.md +++ b/docs/operations/embedded-agents.md @@ -72,8 +72,9 @@ For OpenRouter: ```bash export OPENROUTER_API_KEY=sk-or-... -# Optional; defaults to openai/gpt-5.5 -export OPENROUTER_MODEL=openai/gpt-5.5 +# Optional; defaults to openai/gpt-5.6-luna with reasoning effort high +export OPENROUTER_MODEL=openai/gpt-5.6-luna +export OPENROUTER_REASONING_EFFORT=high # Recommended, and required when multiple provider keys are set export EMBEDDED_AGENT_PROVIDER=openrouter ``` @@ -219,8 +220,9 @@ export OPENAI_MODEL=gpt-4 # Anthropic (default: claude-opus-4-5-20251101) export ANTHROPIC_MODEL=claude-sonnet-4-5-20250929 -# OpenRouter (default: openai/gpt-5.5) +# OpenRouter (default: openai/gpt-5.6-luna @ high) export OPENROUTER_MODEL=anthropic/claude-sonnet-4 +export OPENROUTER_REASONING_EFFORT=medium ``` ### Verify Configuration diff --git a/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts b/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts index 98a0448bf..4604b0714 100644 --- a/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts +++ b/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts @@ -100,7 +100,7 @@ function createTestEnv(): Env { SENTRY_CLIENT_SECRET: "test-client-secret", SENTRY_HOST: "sentry.io", OPENROUTER_API_KEY: "test-openrouter-key", - OPENROUTER_MODEL: "openai/gpt-5.5", + OPENROUTER_MODEL: "openai/gpt-5.6-luna", EMBEDDED_AGENT_PROVIDER: "openrouter", OAUTH_KV: {} as KVNamespace, OAUTH_PROVIDER: { diff --git a/packages/mcp-cloudflare/src/server/routes/chat.ts b/packages/mcp-cloudflare/src/server/routes/chat.ts index 69badf2cf..3ab080f1d 100644 --- a/packages/mcp-cloudflare/src/server/routes/chat.ts +++ b/packages/mcp-cloudflare/src/server/routes/chat.ts @@ -1,6 +1,9 @@ import { experimental_createMCPClient } from "@ai-sdk/mcp"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import { getOpenRouterModel } from "@sentry/mcp-core/internal/agents/openrouter-provider"; +import { + getOpenRouterModel, + getOpenRouterProviderOptions, +} from "@sentry/mcp-core/internal/agents/openrouter-provider"; import { logInfo, logIssue, logWarn } from "@sentry/mcp-core/telem/logging"; import { convertToModelMessages, @@ -335,6 +338,7 @@ Start conversations by exploring what's available in their account. Use tools li Remember: You're a test assistant, not a general-purpose helper. Stay focused on testing the MCP integration with their real data.`, maxOutputTokens: 2000, stopWhen: stepCountIs(10), + providerOptions: getOpenRouterProviderOptions(), experimental_telemetry: { isEnabled: true, }, diff --git a/packages/mcp-cloudflare/src/server/sentry.config.test.ts b/packages/mcp-cloudflare/src/server/sentry.config.test.ts index 42590e473..bd2674dd9 100644 --- a/packages/mcp-cloudflare/src/server/sentry.config.test.ts +++ b/packages/mcp-cloudflare/src/server/sentry.config.test.ts @@ -14,7 +14,7 @@ function createEnv(overrides: Partial = {}): Env { SENTRY_DSN: "https://public@example.ingest.sentry.io/1", SENTRY_HOST: "sentry.io", OPENROUTER_API_KEY: "test-openrouter-key", - OPENROUTER_MODEL: "openai/gpt-5.5", + OPENROUTER_MODEL: "openai/gpt-5.6-luna", EMBEDDED_AGENT_PROVIDER: "openrouter", OAUTH_PROVIDER: {} as Env["OAUTH_PROVIDER"], AI: {} as Ai, diff --git a/packages/mcp-cloudflare/src/server/types.ts b/packages/mcp-cloudflare/src/server/types.ts index 1c9a59751..2a91d6816 100644 --- a/packages/mcp-cloudflare/src/server/types.ts +++ b/packages/mcp-cloudflare/src/server/types.ts @@ -64,6 +64,7 @@ export interface Env { OPENAI_API_KEY?: string; OPENROUTER_API_KEY?: string; OPENROUTER_MODEL?: string; + OPENROUTER_REASONING_EFFORT?: string; EMBEDDED_AGENT_PROVIDER?: string; MCP_URL?: string; OAUTH_PROVIDER: OAuthHelpers; diff --git a/packages/mcp-cloudflare/worker-configuration.d.ts b/packages/mcp-cloudflare/worker-configuration.d.ts index 33dbf79b4..7e9323edf 100644 --- a/packages/mcp-cloudflare/worker-configuration.d.ts +++ b/packages/mcp-cloudflare/worker-configuration.d.ts @@ -13,6 +13,7 @@ declare namespace Cloudflare { OPENAI_API_KEY?: string; OPENROUTER_API_KEY?: string; OPENROUTER_MODEL?: string; + OPENROUTER_REASONING_EFFORT?: string; EMBEDDED_AGENT_PROVIDER?: string; COOKIE_SECRET: string; CHAT_RATE_LIMITER: RateLimit; @@ -27,7 +28,7 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types diff --git a/packages/mcp-cloudflare/wrangler.test.jsonc b/packages/mcp-cloudflare/wrangler.test.jsonc index ef70625fc..cab442cd6 100644 --- a/packages/mcp-cloudflare/wrangler.test.jsonc +++ b/packages/mcp-cloudflare/wrangler.test.jsonc @@ -15,7 +15,7 @@ "SENTRY_CLIENT_SECRET": "test-client-secret", "SENTRY_HOST": "sentry.io", "OPENROUTER_API_KEY": "test-openrouter-key", - "OPENROUTER_MODEL": "openai/gpt-5.5", + "OPENROUTER_MODEL": "openai/gpt-5.6-luna", "EMBEDDED_AGENT_PROVIDER": "openrouter" }, "kv_namespaces": [ diff --git a/packages/mcp-core/README.md b/packages/mcp-core/README.md index 100dc6a70..b58cc8aed 100644 --- a/packages/mcp-core/README.md +++ b/packages/mcp-core/README.md @@ -83,7 +83,8 @@ MCP_ADD_SCOPES=event:write # Add to default scopes (keeps defaults) OPENAI_API_KEY=your-openai-key # Use OpenAI for AI-powered search tools OPENAI_MODEL=gpt-5 # OpenAI model to use (default: "gpt-5") OPENROUTER_API_KEY=your-openrouter-key # Or use OpenRouter for AI-powered search tools -OPENROUTER_MODEL=openai/gpt-5.5 # OpenRouter model to use (default: "openai/gpt-5.5") +OPENROUTER_MODEL=openai/gpt-5.6-luna # OpenRouter model to use (default: "openai/gpt-5.6-luna") +OPENROUTER_REASONING_EFFORT=high # OpenRouter reasoning effort: "none", "low", "medium", "high", "xhigh", "max", or "" to disable (default: "high") OPENAI_REASONING_EFFORT=low # Reasoning effort for o1 models: "low", "medium", "high", or "" to disable (default: "low") # No environment variable exists for the OpenAI base URL override; use --openai-base-url instead. diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts index e179d0b59..1a1f5f0e2 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts @@ -1,15 +1,20 @@ import type { LanguageModelV3 } from "@ai-sdk/provider"; import { generateText } from "ai"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { getOpenRouterModel } from "./openrouter-provider.js"; +import { + getOpenRouterModel, + getOpenRouterProviderOptions, +} from "./openrouter-provider.js"; describe("openrouter-provider", () => { const originalApiKey = process.env.OPENROUTER_API_KEY; const originalModel = process.env.OPENROUTER_MODEL; + const originalReasoningEffort = process.env.OPENROUTER_REASONING_EFFORT; beforeEach(() => { process.env.OPENROUTER_API_KEY = "test-openrouter-key"; delete process.env.OPENROUTER_MODEL; + delete process.env.OPENROUTER_REASONING_EFFORT; }); afterEach(() => { @@ -25,6 +30,12 @@ describe("openrouter-provider", () => { process.env.OPENROUTER_MODEL = originalModel; } + if (originalReasoningEffort === undefined) { + delete process.env.OPENROUTER_REASONING_EFFORT; + } else { + process.env.OPENROUTER_REASONING_EFFORT = originalReasoningEffort; + } + vi.unstubAllGlobals(); }); @@ -74,7 +85,7 @@ describe("openrouter-provider", () => { it("uses default and configured models", () => { expect((getOpenRouterModel() as LanguageModelV3).modelId).toBe( - "openai/gpt-5.5", + "openai/gpt-5.6-luna", ); process.env.OPENROUTER_MODEL = "anthropic/claude-sonnet-4"; @@ -86,4 +97,33 @@ describe("openrouter-provider", () => { (getOpenRouterModel("google/gemini-2.5-pro") as LanguageModelV3).modelId, ).toBe("google/gemini-2.5-pro"); }); + + it("defaults reasoning effort to high and allows override", () => { + expect(getOpenRouterProviderOptions()).toEqual({ + openai: { + structuredOutputs: false, + strictJsonSchema: false, + reasoningEffort: "high", + }, + }); + + process.env.OPENROUTER_REASONING_EFFORT = "medium"; + + expect(getOpenRouterProviderOptions()).toEqual({ + openai: { + structuredOutputs: false, + strictJsonSchema: false, + reasoningEffort: "medium", + }, + }); + + process.env.OPENROUTER_REASONING_EFFORT = ""; + + expect(getOpenRouterProviderOptions()).toEqual({ + openai: { + structuredOutputs: false, + strictJsonSchema: false, + }, + }); + }); }); diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.ts index b987d8691..a666a8e93 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.ts @@ -1,8 +1,10 @@ import { createOpenAI } from "@ai-sdk/openai"; import type { LanguageModel } from "ai"; import { USER_AGENT } from "../../version"; +import type { ProviderOptions } from "./types"; -const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5.5"; +const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5.6-luna"; +const DEFAULT_OPENROUTER_REASONING_EFFORT = "high"; const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; /** @@ -23,3 +25,29 @@ export function getOpenRouterModel(model?: string): LanguageModel { model ?? process.env.OPENROUTER_MODEL ?? DEFAULT_OPENROUTER_MODEL, ); } + +/** + * Provider options for OpenRouter chat-completions calls. + * + * Uses the OpenAI chat provider options shape because OpenRouter is accessed + * through `@ai-sdk/openai` with an OpenRouter base URL. + */ +export function getOpenRouterProviderOptions(): ProviderOptions { + const reasoningEffort = + process.env.OPENROUTER_REASONING_EFFORT ?? + DEFAULT_OPENROUTER_REASONING_EFFORT; + + return { + openai: { + // Required for optional structured-output fields. + // See: https://github.com/getsentry/sentry-mcp/issues/623 + structuredOutputs: false, + strictJsonSchema: false, + ...(reasoningEffort + ? { + reasoningEffort, + } + : {}), + }, + }; +} diff --git a/packages/mcp-core/src/internal/agents/provider-factory.ts b/packages/mcp-core/src/internal/agents/provider-factory.ts index e057d48f5..a324ca348 100644 --- a/packages/mcp-core/src/internal/agents/provider-factory.ts +++ b/packages/mcp-core/src/internal/agents/provider-factory.ts @@ -1,13 +1,16 @@ -import type { EmbeddedAgentProvider, AgentProviderType } from "./types"; +import { ConfigurationError } from "../../errors"; +import { getAnthropicModel, setAnthropicBaseUrl } from "./anthropic-provider"; import { getAzureOpenAIApiSurface, getAzureOpenAIModel, setAzureOpenAIBaseUrl, } from "./azure-openai-provider"; import { getOpenAIModel, setOpenAIBaseUrl } from "./openai-provider"; -import { getAnthropicModel, setAnthropicBaseUrl } from "./anthropic-provider"; -import { getOpenRouterModel } from "./openrouter-provider"; -import { ConfigurationError } from "../../errors"; +import { + getOpenRouterModel, + getOpenRouterProviderOptions, +} from "./openrouter-provider"; +import type { AgentProviderType, EmbeddedAgentProvider } from "./types"; // Module-level state for explicit provider selection let configuredProvider: AgentProviderType | undefined; @@ -128,12 +131,7 @@ function buildProvider(type: AgentProviderType): EmbeddedAgentProvider { type: "openrouter", label: "openrouter", getModel: getOpenRouterModel, - getProviderOptions: () => ({ - openai: { - structuredOutputs: false, - strictJsonSchema: false, - }, - }), + getProviderOptions: getOpenRouterProviderOptions, }; } } diff --git a/packages/mcp-server/src/cli/usage.ts b/packages/mcp-server/src/cli/usage.ts index f7a5b4074..479230a26 100644 --- a/packages/mcp-server/src/cli/usage.ts +++ b/packages/mcp-server/src/cli/usage.ts @@ -47,7 +47,8 @@ Environment variables: OPENAI_API_KEY OpenAI API key for AI-powered search tools ANTHROPIC_API_KEY Anthropic API key for AI-powered search tools OPENROUTER_API_KEY OpenRouter API key for AI-powered search tools - OPENROUTER_MODEL OpenRouter model (default: openai/gpt-5.5) + OPENROUTER_MODEL OpenRouter model (default: openai/gpt-5.6-luna) + OPENROUTER_REASONING_EFFORT OpenRouter reasoning effort (default: high) EMBEDDED_AGENT_PROVIDER Provider override: openai, azure-openai, anthropic, or openrouter MCP_DISABLE_SKILLS Disable specific skills (comma-separated) diff --git a/packages/mcp-test-client/README.md b/packages/mcp-test-client/README.md index 311cc4cf3..cbbd59c3f 100644 --- a/packages/mcp-test-client/README.md +++ b/packages/mcp-test-client/README.md @@ -62,7 +62,7 @@ SENTRY_ACCESS_TOKEN=your_sentry_access_token # Leave unset to target the SaaS host SENTRY_HOST=sentry.example.com # Hostname only MCP_URL=https://mcp.sentry.dev # MCP server host (defaults to production) -MCP_MODEL=openai/gpt-5.5 # Override default model +MCP_MODEL=openai/gpt-5.6-luna # Override default model # Optional - Error tracking SENTRY_DSN=your_sentry_dsn # Error tracking for the client itself diff --git a/packages/mcp-test-client/src/agent.test.ts b/packages/mcp-test-client/src/agent.test.ts index af1f89ba3..81caeee84 100644 --- a/packages/mcp-test-client/src/agent.test.ts +++ b/packages/mcp-test-client/src/agent.test.ts @@ -9,12 +9,12 @@ function createStreamingResponse() { start(controller) { controller.enqueue( encoder.encode( - 'data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":0,"model":"openai/gpt-5.5","choices":[{"index":0,"delta":{"content":"ok"},"finish_reason":null}]}\n\n', + 'data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":0,"model":"openai/gpt-5.6-luna","choices":[{"index":0,"delta":{"content":"ok"},"finish_reason":null}]}\n\n', ), ); controller.enqueue( encoder.encode( - 'data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":0,"model":"openai/gpt-5.5","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', + 'data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":0,"model":"openai/gpt-5.6-luna","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', ), ); controller.enqueue(encoder.encode("data: [DONE]\n\n")); @@ -102,7 +102,7 @@ describe("runAgent", () => { expect(requestUrl).toBe("https://openrouter.ai/api/v1/chat/completions"); expect(authorization).toBe("Bearer sk-or-test"); expect(requestBody).toMatchObject({ - model: "openai/gpt-5.5", + model: "openai/gpt-5.6-luna", stream: true, }); }); diff --git a/packages/mcp-test-client/src/constants.ts b/packages/mcp-test-client/src/constants.ts index b7403fa03..c403881e9 100644 --- a/packages/mcp-test-client/src/constants.ts +++ b/packages/mcp-test-client/src/constants.ts @@ -2,7 +2,7 @@ export const DEFAULT_MCP_URL = "https://mcp.sentry.dev"; export const DEFAULT_OPENAI_MODEL = "gpt-4o"; -export const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5.5"; +export const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5.6-luna"; export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; // OAuth configuration. Overridable via MCP_OAUTH_PORT, MCP_OAUTH_HOST, and diff --git a/turbo.json b/turbo.json index a7e9ed827..b1e67b911 100644 --- a/turbo.json +++ b/turbo.json @@ -98,6 +98,7 @@ "OPENAI_API_KEY", "OPENROUTER_API_KEY", "OPENROUTER_MODEL", + "OPENROUTER_REASONING_EFFORT", "COOKIE_SECRET", "SENTRY_CLIENT_ID", "SENTRY_CLIENT_SECRET", From fb73558f5eb70f3295b53f686bd9b28bb03c0828 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:49:41 +0000 Subject: [PATCH 3/6] fix(mcp): raise chat maxOutputTokens for high reasoning High reasoning effort spends completion budget before visible text or tool calls. Bump the playground cap so luna@high does not truncate. --- packages/mcp-cloudflare/src/server/routes/chat.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/mcp-cloudflare/src/server/routes/chat.ts b/packages/mcp-cloudflare/src/server/routes/chat.ts index 3ab080f1d..1c16f4818 100644 --- a/packages/mcp-cloudflare/src/server/routes/chat.ts +++ b/packages/mcp-cloudflare/src/server/routes/chat.ts @@ -336,7 +336,9 @@ Start conversations by exploring what's available in their account. Use tools li - \`get_sentry_resource\` to dive deep into a specific issue, event, or trace Remember: You're a test assistant, not a general-purpose helper. Stay focused on testing the MCP integration with their real data.`, - maxOutputTokens: 2000, + // High reasoning effort consumes completion budget before visible text/tool + // calls, so keep headroom above the old non-reasoning 2k cap. + maxOutputTokens: 16000, stopWhen: stepCountIs(10), providerOptions: getOpenRouterProviderOptions(), experimental_telemetry: { From cc5b0a299cc349ac4ed5a106015aa58f13390439 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:57:45 +0000 Subject: [PATCH 4/6] fix(mcp): default OpenRouter reasoning effort to medium MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Luna agents are schema-constrained NL→query hops, not long-horizon coding. Start at OpenRouter's medium default and keep OPENROUTER_REASONING_EFFORT for a no-deploy bump if quality dips. Co-Authored-By: David Cramer --- README.md | 2 +- docs/operations/embedded-agents.md | 8 ++++---- packages/mcp-cloudflare/src/server/routes/chat.ts | 2 +- packages/mcp-core/README.md | 2 +- .../src/internal/agents/openrouter-provider.test.ts | 8 ++++---- .../mcp-core/src/internal/agents/openrouter-provider.ts | 2 +- packages/mcp-server/src/cli/usage.ts | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 720edc6e5..1d904925b 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ OPENAI_API_KEY= # Required if using OpenAI ANTHROPIC_API_KEY= # Required if using Anthropic OPENROUTER_API_KEY= # Required if using OpenRouter OPENROUTER_MODEL= # Optional OpenRouter model, defaults to 'openai/gpt-5.6-luna' -OPENROUTER_REASONING_EFFORT= # Optional OpenRouter reasoning effort, defaults to 'high' +OPENROUTER_REASONING_EFFORT= # Optional OpenRouter reasoning effort, defaults to 'medium' # Optional overrides SENTRY_HOST= # For self-hosted deployments diff --git a/docs/operations/embedded-agents.md b/docs/operations/embedded-agents.md index 2f4f154e4..2ba1c445f 100644 --- a/docs/operations/embedded-agents.md +++ b/docs/operations/embedded-agents.md @@ -72,9 +72,9 @@ For OpenRouter: ```bash export OPENROUTER_API_KEY=sk-or-... -# Optional; defaults to openai/gpt-5.6-luna with reasoning effort high +# Optional; defaults to openai/gpt-5.6-luna with reasoning effort medium export OPENROUTER_MODEL=openai/gpt-5.6-luna -export OPENROUTER_REASONING_EFFORT=high +export OPENROUTER_REASONING_EFFORT=medium # Recommended, and required when multiple provider keys are set export EMBEDDED_AGENT_PROVIDER=openrouter ``` @@ -220,9 +220,9 @@ export OPENAI_MODEL=gpt-4 # Anthropic (default: claude-opus-4-5-20251101) export ANTHROPIC_MODEL=claude-sonnet-4-5-20250929 -# OpenRouter (default: openai/gpt-5.6-luna @ high) +# OpenRouter (default: openai/gpt-5.6-luna @ medium) export OPENROUTER_MODEL=anthropic/claude-sonnet-4 -export OPENROUTER_REASONING_EFFORT=medium +export OPENROUTER_REASONING_EFFORT=high ``` ### Verify Configuration diff --git a/packages/mcp-cloudflare/src/server/routes/chat.ts b/packages/mcp-cloudflare/src/server/routes/chat.ts index 1c16f4818..b66f1539e 100644 --- a/packages/mcp-cloudflare/src/server/routes/chat.ts +++ b/packages/mcp-cloudflare/src/server/routes/chat.ts @@ -336,7 +336,7 @@ Start conversations by exploring what's available in their account. Use tools li - \`get_sentry_resource\` to dive deep into a specific issue, event, or trace Remember: You're a test assistant, not a general-purpose helper. Stay focused on testing the MCP integration with their real data.`, - // High reasoning effort consumes completion budget before visible text/tool + // Reasoning effort can consume completion budget before visible text/tool // calls, so keep headroom above the old non-reasoning 2k cap. maxOutputTokens: 16000, stopWhen: stepCountIs(10), diff --git a/packages/mcp-core/README.md b/packages/mcp-core/README.md index b58cc8aed..e11eb16c0 100644 --- a/packages/mcp-core/README.md +++ b/packages/mcp-core/README.md @@ -84,7 +84,7 @@ OPENAI_API_KEY=your-openai-key # Use OpenAI for AI-powered search tools OPENAI_MODEL=gpt-5 # OpenAI model to use (default: "gpt-5") OPENROUTER_API_KEY=your-openrouter-key # Or use OpenRouter for AI-powered search tools OPENROUTER_MODEL=openai/gpt-5.6-luna # OpenRouter model to use (default: "openai/gpt-5.6-luna") -OPENROUTER_REASONING_EFFORT=high # OpenRouter reasoning effort: "none", "low", "medium", "high", "xhigh", "max", or "" to disable (default: "high") +OPENROUTER_REASONING_EFFORT=medium # OpenRouter reasoning effort: "none", "low", "medium", "high", "xhigh", "max", or "" to disable (default: "medium") OPENAI_REASONING_EFFORT=low # Reasoning effort for o1 models: "low", "medium", "high", or "" to disable (default: "low") # No environment variable exists for the OpenAI base URL override; use --openai-base-url instead. diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts index 1a1f5f0e2..7eabadb88 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts @@ -98,22 +98,22 @@ describe("openrouter-provider", () => { ).toBe("google/gemini-2.5-pro"); }); - it("defaults reasoning effort to high and allows override", () => { + it("defaults reasoning effort to medium and allows override", () => { expect(getOpenRouterProviderOptions()).toEqual({ openai: { structuredOutputs: false, strictJsonSchema: false, - reasoningEffort: "high", + reasoningEffort: "medium", }, }); - process.env.OPENROUTER_REASONING_EFFORT = "medium"; + process.env.OPENROUTER_REASONING_EFFORT = "high"; expect(getOpenRouterProviderOptions()).toEqual({ openai: { structuredOutputs: false, strictJsonSchema: false, - reasoningEffort: "medium", + reasoningEffort: "high", }, }); diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.ts index a666a8e93..395bcff0b 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.ts @@ -4,7 +4,7 @@ import { USER_AGENT } from "../../version"; import type { ProviderOptions } from "./types"; const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5.6-luna"; -const DEFAULT_OPENROUTER_REASONING_EFFORT = "high"; +const DEFAULT_OPENROUTER_REASONING_EFFORT = "medium"; const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; /** diff --git a/packages/mcp-server/src/cli/usage.ts b/packages/mcp-server/src/cli/usage.ts index 479230a26..2d362b7df 100644 --- a/packages/mcp-server/src/cli/usage.ts +++ b/packages/mcp-server/src/cli/usage.ts @@ -48,7 +48,7 @@ Environment variables: ANTHROPIC_API_KEY Anthropic API key for AI-powered search tools OPENROUTER_API_KEY OpenRouter API key for AI-powered search tools OPENROUTER_MODEL OpenRouter model (default: openai/gpt-5.6-luna) - OPENROUTER_REASONING_EFFORT OpenRouter reasoning effort (default: high) + OPENROUTER_REASONING_EFFORT OpenRouter reasoning effort (default: medium) EMBEDDED_AGENT_PROVIDER Provider override: openai, azure-openai, anthropic, or openrouter MCP_DISABLE_SKILLS Disable specific skills (comma-separated) From f81f71019637d774883e8af6467bb69dc1c32e4a Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:07:11 +0000 Subject: [PATCH 5/6] fix(mcp): validate OpenRouter reasoning effort values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @ai-sdk/openai only accepts none/minimal/low/medium/high/xhigh. Reject unknown OPENROUTER_REASONING_EFFORT values early, map max→xhigh, and document empty string as omit-provider-option rather than disable. --- packages/mcp-core/README.md | 2 +- .../agents/openrouter-provider.test.ts | 20 +++++++ .../internal/agents/openrouter-provider.ts | 58 ++++++++++++++++++- packages/mcp-server/src/cli/usage.ts | 2 +- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/packages/mcp-core/README.md b/packages/mcp-core/README.md index e11eb16c0..0e7ad46ba 100644 --- a/packages/mcp-core/README.md +++ b/packages/mcp-core/README.md @@ -84,7 +84,7 @@ OPENAI_API_KEY=your-openai-key # Use OpenAI for AI-powered search tools OPENAI_MODEL=gpt-5 # OpenAI model to use (default: "gpt-5") OPENROUTER_API_KEY=your-openrouter-key # Or use OpenRouter for AI-powered search tools OPENROUTER_MODEL=openai/gpt-5.6-luna # OpenRouter model to use (default: "openai/gpt-5.6-luna") -OPENROUTER_REASONING_EFFORT=medium # OpenRouter reasoning effort: "none", "low", "medium", "high", "xhigh", "max", or "" to disable (default: "medium") +OPENROUTER_REASONING_EFFORT=medium # OpenRouter reasoning effort: "none", "minimal", "low", "medium", "high", "xhigh" ("max" aliases xhigh), or "" to omit (default: "medium") OPENAI_REASONING_EFFORT=low # Reasoning effort for o1 models: "low", "medium", "high", or "" to disable (default: "low") # No environment variable exists for the OpenAI base URL override; use --openai-base-url instead. diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts index 7eabadb88..d9b36bfba 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts @@ -1,6 +1,7 @@ import type { LanguageModelV3 } from "@ai-sdk/provider"; import { generateText } from "ai"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { ConfigurationError } from "../../errors.js"; import { getOpenRouterModel, getOpenRouterProviderOptions, @@ -126,4 +127,23 @@ describe("openrouter-provider", () => { }, }); }); + + it("maps max to xhigh and rejects unknown reasoning effort", () => { + process.env.OPENROUTER_REASONING_EFFORT = "max"; + + expect(getOpenRouterProviderOptions()).toEqual({ + openai: { + structuredOutputs: false, + strictJsonSchema: false, + reasoningEffort: "xhigh", + }, + }); + + process.env.OPENROUTER_REASONING_EFFORT = "ludicrous"; + + expect(() => getOpenRouterProviderOptions()).toThrow(ConfigurationError); + expect(() => getOpenRouterProviderOptions()).toThrow( + /Invalid OPENROUTER_REASONING_EFFORT/, + ); + }); }); diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.ts index 395bcff0b..b80e98b14 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.ts @@ -1,5 +1,6 @@ import { createOpenAI } from "@ai-sdk/openai"; import type { LanguageModel } from "ai"; +import { ConfigurationError } from "../../errors"; import { USER_AGENT } from "../../version"; import type { ProviderOptions } from "./types"; @@ -7,6 +8,57 @@ const DEFAULT_OPENROUTER_MODEL = "openai/gpt-5.6-luna"; const DEFAULT_OPENROUTER_REASONING_EFFORT = "medium"; const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; +// Matches @ai-sdk/openai chat providerOptions.openai.reasoningEffort. +const OPENROUTER_REASONING_EFFORTS = [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", +] as const; + +type OpenRouterReasoningEffort = (typeof OPENROUTER_REASONING_EFFORTS)[number]; + +const OPENROUTER_REASONING_EFFORT_ALIASES: Record< + string, + OpenRouterReasoningEffort +> = { + // OpenRouter sometimes documents "max"; AI SDK accepts "xhigh". + max: "xhigh", +}; + +function resolveOpenRouterReasoningEffort( + value: string | undefined, +): OpenRouterReasoningEffort | undefined { + if (value === undefined) { + return DEFAULT_OPENROUTER_REASONING_EFFORT; + } + + // Empty string omits the provider option so the model default applies. + if (value === "") { + return undefined; + } + + const normalized = value.trim().toLowerCase(); + const aliased = OPENROUTER_REASONING_EFFORT_ALIASES[normalized]; + if (aliased) { + return aliased; + } + + if ( + (OPENROUTER_REASONING_EFFORTS as readonly string[]).includes(normalized) + ) { + return normalized as OpenRouterReasoningEffort; + } + + throw new ConfigurationError( + `Invalid OPENROUTER_REASONING_EFFORT "${value}". Expected one of: ${OPENROUTER_REASONING_EFFORTS.join( + ", ", + )}, max (alias for xhigh), or "" to omit.`, + ); +} + /** * Builds an OpenRouter chat-completions model for embedded agent calls. */ @@ -33,9 +85,9 @@ export function getOpenRouterModel(model?: string): LanguageModel { * through `@ai-sdk/openai` with an OpenRouter base URL. */ export function getOpenRouterProviderOptions(): ProviderOptions { - const reasoningEffort = - process.env.OPENROUTER_REASONING_EFFORT ?? - DEFAULT_OPENROUTER_REASONING_EFFORT; + const reasoningEffort = resolveOpenRouterReasoningEffort( + process.env.OPENROUTER_REASONING_EFFORT, + ); return { openai: { diff --git a/packages/mcp-server/src/cli/usage.ts b/packages/mcp-server/src/cli/usage.ts index 2d362b7df..793d77853 100644 --- a/packages/mcp-server/src/cli/usage.ts +++ b/packages/mcp-server/src/cli/usage.ts @@ -48,7 +48,7 @@ Environment variables: ANTHROPIC_API_KEY Anthropic API key for AI-powered search tools OPENROUTER_API_KEY OpenRouter API key for AI-powered search tools OPENROUTER_MODEL OpenRouter model (default: openai/gpt-5.6-luna) - OPENROUTER_REASONING_EFFORT OpenRouter reasoning effort (default: medium) + OPENROUTER_REASONING_EFFORT OpenRouter reasoning effort: none|minimal|low|medium|high|xhigh (max→xhigh), or "" to omit (default: medium) EMBEDDED_AGENT_PROVIDER Provider override: openai, azure-openai, anthropic, or openrouter MCP_DISABLE_SKILLS Disable specific skills (comma-separated) From 059f28a886f7a78521979d2c02841ce83adc5e18 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:44:31 +0000 Subject: [PATCH 6/6] fix(mcp): harden OpenRouter reasoning-effort alias lookup Use a null-prototype alias map and Object.hasOwn so prototype keys like constructor cannot bypass validation and leak a function into provider options. --- .../src/internal/agents/openrouter-provider.test.ts | 5 +++++ .../src/internal/agents/openrouter-provider.ts | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts index d9b36bfba..42ed9a14c 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.test.ts @@ -145,5 +145,10 @@ describe("openrouter-provider", () => { expect(() => getOpenRouterProviderOptions()).toThrow( /Invalid OPENROUTER_REASONING_EFFORT/, ); + + // Prototype keys must not bypass validation via inherited Object props. + process.env.OPENROUTER_REASONING_EFFORT = "constructor"; + + expect(() => getOpenRouterProviderOptions()).toThrow(ConfigurationError); }); }); diff --git a/packages/mcp-core/src/internal/agents/openrouter-provider.ts b/packages/mcp-core/src/internal/agents/openrouter-provider.ts index b80e98b14..1dca25fc5 100644 --- a/packages/mcp-core/src/internal/agents/openrouter-provider.ts +++ b/packages/mcp-core/src/internal/agents/openrouter-provider.ts @@ -20,13 +20,14 @@ const OPENROUTER_REASONING_EFFORTS = [ type OpenRouterReasoningEffort = (typeof OPENROUTER_REASONING_EFFORTS)[number]; +// Null prototype so Object.prototype keys like "constructor" cannot slip through. const OPENROUTER_REASONING_EFFORT_ALIASES: Record< string, OpenRouterReasoningEffort -> = { +> = Object.assign(Object.create(null), { // OpenRouter sometimes documents "max"; AI SDK accepts "xhigh". max: "xhigh", -}; +}); function resolveOpenRouterReasoningEffort( value: string | undefined, @@ -41,9 +42,8 @@ function resolveOpenRouterReasoningEffort( } const normalized = value.trim().toLowerCase(); - const aliased = OPENROUTER_REASONING_EFFORT_ALIASES[normalized]; - if (aliased) { - return aliased; + if (Object.hasOwn(OPENROUTER_REASONING_EFFORT_ALIASES, normalized)) { + return OPENROUTER_REASONING_EFFORT_ALIASES[normalized]; } if (