diff --git a/docs/openapi/openapi.json b/docs/openapi/openapi.json index 4a651185331..4cd81e8fbd2 100644 --- a/docs/openapi/openapi.json +++ b/docs/openapi/openapi.json @@ -42703,7 +42703,7 @@ { "name": "state", "in": "query", - "description": "Comma-separated runtime connection states to include (OR semantics),\nresolved against live engine state. `connected` matches clients the engine\ncurrently reports as connected; `disconnected` matches everything else.\n", + "description": "Comma-separated runtime connection states to include (OR semantics),\nresolved against live engine state. Only `connected` and\n`disconnected` are meaningful filter values: `connected` matches\nclients the engine currently reports as connected; `disconnected`\nmatches everything else (error, pending states, needs_reauth,\ndisabled, not present in the engine). Selecting both, or neither,\napplies no state filter. Note the response-only needs_reauth\nprojection on per-user clients happens after filtering, so such\nclients still match `connected`.\n", "schema": { "type": "string", "example": "connected" @@ -42818,7 +42818,7 @@ "post": { "operationId": "addMCPClient", "summary": "Add MCP client", - "description": "Adds a new MCP client with the specified configuration.\nNote: tool_pricing is not available when creating a new client as tools are fetched after client creation.\n", + "description": "Adds a new MCP client with the specified configuration.\nNote: tool_pricing is not available when creating a new client; tool\npricing can only be set once the tool list is known. For shared-connection\nclients tools are fetched after client creation; for per-user auth types\nthey are discovered during the create/verify flow itself.\n", "tags": [ "MCP" ], @@ -42883,6 +42883,22 @@ "default": true, "description": "Whether the MCP server supports ping for health checks.\nIf true, uses lightweight ping method for health checks.\nIf false, uses listTools method for health checks instead.\n" }, + "tool_sync_interval": { + "type": "integer", + "description": "Per-client tool-list sync interval in minutes. 0 (or omitted) falls back\nto the global mcp_tool_sync_interval client config; a negative value\ndisables periodic tool sync for this client.\n" + }, + "tool_execution_timeout": { + "type": "integer", + "minimum": 0, + "description": "Per-client tool execution timeout in seconds. 0 (or omitted) falls back\nto the global mcp_tool_execution_timeout client config.\n" + }, + "allowed_extra_headers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowlist of request-level headers callers may forward to this MCP\nserver at execution time.\n[\"*\"] => any header may be forwarded\n[] or omitted => no extra headers are forwarded\n[\"header1\", \"header2\"] => only the specified headers\n" + }, "connection_type": { "type": "string", "enum": [ @@ -42964,7 +42980,7 @@ "additionalProperties": { "type": "string" }, - "description": "Used only at create time when `auth_type` is `per_user_headers`. A sample\nset of header values the admin supplies so Bifrost can run a one-time\nupstream verify and discover the tool list. Discarded after the create\ncall — not persisted. Mirrors how the admin's temp OAuth token is used\nfor `per_user_oauth` setup.\n" + "description": "Used only at create time when `auth_type` is `per_user_headers`. A sample\nset of header values the admin supplies so Bifrost can run a one-time\nupstream verify and discover the tool list. Not persisted by the create\ncall; each end-user submits their own values at runtime. To have Bifrost\nretain an admin discovery credential for periodic tool-list refresh,\nuse POST /api/mcp/client/{id}/verify-headers, which stores the sample\nvalues as that credential.\n" } } }, @@ -43043,6 +43059,22 @@ "default": true, "description": "Whether the MCP server supports ping for health checks.\nIf true, uses lightweight ping method for health checks.\nIf false, uses listTools method for health checks instead.\n" }, + "tool_sync_interval": { + "type": "integer", + "description": "Per-client tool-list sync interval in minutes. 0 (or omitted) falls back\nto the global mcp_tool_sync_interval client config; a negative value\ndisables periodic tool sync for this client.\n" + }, + "tool_execution_timeout": { + "type": "integer", + "minimum": 0, + "description": "Per-client tool execution timeout in seconds. 0 (or omitted) falls back\nto the global mcp_tool_execution_timeout client config.\n" + }, + "allowed_extra_headers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowlist of request-level headers callers may forward to this MCP\nserver at execution time.\n[\"*\"] => any header may be forwarded\n[] or omitted => no extra headers are forwarded\n[\"header1\", \"header2\"] => only the specified headers\n" + }, "connection_type": { "type": "string", "enum": [ @@ -43124,7 +43156,7 @@ "additionalProperties": { "type": "string" }, - "description": "Used only at create time when `auth_type` is `per_user_headers`. A sample\nset of header values the admin supplies so Bifrost can run a one-time\nupstream verify and discover the tool list. Discarded after the create\ncall — not persisted. Mirrors how the admin's temp OAuth token is used\nfor `per_user_oauth` setup.\n" + "description": "Used only at create time when `auth_type` is `per_user_headers`. A sample\nset of header values the admin supplies so Bifrost can run a one-time\nupstream verify and discover the tool list. Not persisted by the create\ncall; each end-user submits their own values at runtime. To have Bifrost\nretain an admin discovery credential for periodic tool-list refresh,\nuse POST /api/mcp/client/{id}/verify-headers, which stores the sample\nvalues as that credential.\n" } } }, @@ -43203,6 +43235,22 @@ "default": true, "description": "Whether the MCP server supports ping for health checks.\nIf true, uses lightweight ping method for health checks.\nIf false, uses listTools method for health checks instead.\n" }, + "tool_sync_interval": { + "type": "integer", + "description": "Per-client tool-list sync interval in minutes. 0 (or omitted) falls back\nto the global mcp_tool_sync_interval client config; a negative value\ndisables periodic tool sync for this client.\n" + }, + "tool_execution_timeout": { + "type": "integer", + "minimum": 0, + "description": "Per-client tool execution timeout in seconds. 0 (or omitted) falls back\nto the global mcp_tool_execution_timeout client config.\n" + }, + "allowed_extra_headers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowlist of request-level headers callers may forward to this MCP\nserver at execution time.\n[\"*\"] => any header may be forwarded\n[] or omitted => no extra headers are forwarded\n[\"header1\", \"header2\"] => only the specified headers\n" + }, "connection_type": { "type": "string", "enum": [ @@ -43284,7 +43332,7 @@ "additionalProperties": { "type": "string" }, - "description": "Used only at create time when `auth_type` is `per_user_headers`. A sample\nset of header values the admin supplies so Bifrost can run a one-time\nupstream verify and discover the tool list. Discarded after the create\ncall — not persisted. Mirrors how the admin's temp OAuth token is used\nfor `per_user_oauth` setup.\n" + "description": "Used only at create time when `auth_type` is `per_user_headers`. A sample\nset of header values the admin supplies so Bifrost can run a one-time\nupstream verify and discover the tool list. Not persisted by the create\ncall; each end-user submits their own values at runtime. To have Bifrost\nretain an admin discovery credential for periodic tool-list refresh,\nuse POST /api/mcp/client/{id}/verify-headers, which stores the sample\nvalues as that credential.\n" } } }, @@ -43392,7 +43440,7 @@ "put": { "operationId": "editMCPClient", "summary": "Edit MCP client", - "description": "Updates an existing MCP client's configuration.\nUnlike client creation, tool_pricing can be included to set per-tool execution costs since tools are already fetched.\nOptionally provide vk_configs to manage which virtual keys have access to this MCP server and with which tools. When provided, this fully replaces all existing VK assignments in a single atomic transaction.\nSet disabled: true to shut down the client's connection and workers without removing it. Set disabled: false to reconnect a previously disabled client.\n", + "description": "Updates an existing MCP client's configuration. All fields are optional\n(PATCH semantics); connection_type, auth_type, connection_string,\nstdio_config, and oauth_config_id are immutable after creation.\nUnlike client creation, tool_pricing can be included to set per-tool execution costs since tools are already fetched.\nFor OAuth-based clients, providing oauth_config rotates the stored OAuth\nconfiguration in place and flips every bound token to needs_reauth when\na field actually changes (see MCPClientUpdateRequest.oauth_config).\nOptionally provide vk_configs to manage which virtual keys have access to this MCP server and with which tools. When provided, this fully replaces all existing VK assignments in a single atomic transaction.\nSet disabled: true to shut down the client's connection and workers without removing it. Set disabled: false to reconnect a previously disabled client.\n", "tags": [ "MCP" ], @@ -43413,41 +43461,8 @@ "application/json": { "schema": { "type": "object", - "description": "MCP client configuration for updating an existing client (includes tool_pricing)", - "allOf": [ - { - "if": { - "required": [ - "auth_type" - ], - "properties": { - "auth_type": { - "const": "per_user_headers" - } - } - }, - "then": { - "required": [ - "per_user_header_keys" - ], - "properties": { - "per_user_header_keys": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - } - } - } - } - ], + "description": "MCP client configuration for updating an existing client (includes tool_pricing).\nAll fields are optional; omitting a field retains its existing value (PATCH\nsemantics). Immutable fields (connection_type, auth_type, connection_string,\nstdio_config, oauth_config_id) are not accepted here; they cannot be changed\nafter creation.\n", "properties": { - "client_id": { - "type": "string", - "description": "Unique identifier for the MCP client" - }, "name": { "type": "string", "description": "Display name for the MCP client" @@ -43456,65 +43471,36 @@ "type": "boolean", "description": "Whether this client is available in code mode" }, - "connection_type": { - "type": "string", - "enum": [ - "http", - "stdio", - "sse", - "inprocess" - ], - "description": "Connection type for MCP client" - }, - "connection_string": { - "type": "string", - "description": "HTTP or SSE URL (required for HTTP or SSE connections)" - }, - "stdio_config": { - "type": "object", - "description": "STDIO configuration for MCP client", - "properties": { - "command": { - "type": "string", - "description": "Executable command to run" - }, - "args": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Command line arguments" - }, - "envs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Environment variables required" - } - } + "is_ping_available": { + "type": "boolean", + "description": "Whether the MCP server supports ping for health checks.\nIf true, uses lightweight ping method for health checks.\nIf false, uses listTools method for health checks instead.\n" }, - "auth_type": { - "type": "string", - "enum": [ - "none", - "headers", - "oauth", - "per_user_oauth", - "per_user_headers" - ], - "description": "Authentication type for the MCP connection" + "tool_sync_interval": { + "type": "integer", + "description": "Per-client tool-list sync interval in minutes. 0 falls back to the\nglobal mcp_tool_sync_interval client config; a negative value disables\nperiodic tool sync for this client.\n" }, - "oauth_config_id": { - "type": "string", - "description": "OAuth config ID for OAuth authentication.\nReferences the oauth_configs table.\nOnly relevant when auth_type is \"oauth\".\n" + "tool_execution_timeout": { + "type": "integer", + "minimum": 0, + "description": "Per-client tool execution timeout in seconds. 0 falls back to the\nglobal mcp_tool_execution_timeout client config.\n" }, "headers": { "type": "object", "additionalProperties": { "type": "string" }, - "description": "Custom headers to include in requests.\nOnly used when auth_type is \"headers\".\n" + "description": "Custom headers to include in requests.\nOnly used when auth_type is \"headers\". Supports env./vault. references.\nValues echoed back redacted from a GET response are recognized and\npreserve the stored value, so a fetch-modify-put round trip is safe.\n" + }, + "allowed_extra_headers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowlist of request-level headers callers may forward to this MCP\nserver at execution time.\n[\"*\"] => any header may be forwarded\n[] => no extra headers are forwarded\n[\"header1\", \"header2\"] => only the specified headers\n" + }, + "oauth_config": { + "$ref": "#/components/schemas/OAuthConfigRequest", + "description": "Rotates the stored OAuth configuration in place. Only accepted for\nauth_type \"oauth\" or \"per_user_oauth\" (400 otherwise). Any provided\nfield replaces the stored value; no new config row is created and no\nre-discovery or re-registration runs. Unset fields preserve stored\nvalues: client_id/client_secret follow the SecretVar masked-placeholder\nconvention (send back the redacted placeholder from a GET response to\nkeep the stored secret), while empty strings / empty arrays on the\nremaining fields mean \"not provided\".\n\nWhen any field actually changes, every token bound to the OAuth config\nis flipped to needs_reauth regardless of auth mode, shared and\nper-user sessions alike must re-authenticate. A round trip that\nresolves to the stored values is a no-op and does not cascade.\nRotation cannot run while the client is (or is being) disabled\n(400); send those as two separate requests.\n" }, "tools_to_execute": { "type": "array", @@ -43548,7 +43534,7 @@ "items": { "type": "string" }, - "description": "For `per_user_headers` clients only. Updating this list flips every existing\nactive per-user credential row to `needs_update`; callers will be sent back to\nthe submission form on their next tool call to satisfy the new schema.\n" + "description": "For `per_user_headers` clients only. Cannot be set to an empty list.\nUpdating this list flips every existing active per-user credential row\nto `needs_update`; callers will be sent back to the submission form on\ntheir next tool call to satisfy the new schema. The retained admin\ndiscovery credential is flipped the same way; repair it by calling\nPOST /api/mcp/client/{id}/verify-headers with fresh sample values.\n" }, "disabled": { "type": "boolean", @@ -43756,7 +43742,7 @@ "post": { "operationId": "completeMCPClientOAuth", "summary": "Complete MCP client OAuth flow", - "description": "Completes the OAuth flow for an MCP client after the user has authorized the request.\nThis endpoint should be called after the OAuth provider redirects back to the callback endpoint\nand the OAuth token has been stored. It retrieves the pending MCP client configuration and\nestablishes the connection with the OAuth-provided credentials.\n", + "description": "Completes an OAuth flow for an MCP client after the admin has authorized\nthe request upstream. Call it once the flow's status_url reports\n\"authorized\". It serves every admin-side OAuth completion with one\nendpoint:\n\n- Create-time and config.json-bootstrap flows: retrieves the pending MCP\n client configuration and establishes the connection with the\n OAuth-provided credentials (per_user_oauth clients instead verify with\n the admin token, discover tools, and retain the token as the admin\n discovery credential).\n- Reauthorize flows (started via POST /api/mcp/client/{id}/reauthorize):\n for shared \"oauth\" clients, reconnects the client with the fresh\n credential; for per_user_oauth clients, verifies the fresh admin token\n upstream, re-discovers tools, and promotes it to the retained admin\n discovery credential.\n\nReplays are rejected with 409: hitting the endpoint again after the flow\nalready completed (no pending configuration and no freshly-written\ntoken) returns \"OAuth flow has already been completed\".\n", "tags": [ "MCP", "OAuth" @@ -43766,7 +43752,7 @@ "name": "id", "in": "path", "required": true, - "description": "MCP client ID", + "description": "The oauth_config_id of the flow being completed (as returned in the\ninitiation response's oauth_config_id / complete_url), not the MCP\nclient ID.\n", "schema": { "type": "string" } @@ -43779,7 +43765,7 @@ ], "responses": { "200": { - "description": "MCP client connected successfully with OAuth", + "description": "MCP client connected (or re-authorized) successfully with OAuth", "content": { "application/json": { "schema": { @@ -43789,7 +43775,7 @@ } }, "400": { - "description": "OAuth not authorized yet or MCP client not found in pending OAuth clients", + "description": "OAuth flow not authorized yet, or the OAuth config does not belong to an OAuth-based MCP client", "content": { "application/json": { "schema": { @@ -43799,7 +43785,17 @@ } }, "404": { - "description": "MCP client not found in pending OAuth clients or OAuth config not found", + "description": "OAuth config not found, or no MCP client is linked to this OAuth flow", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BifrostError" + } + } + } + }, + "409": { + "description": "OAuth flow has already been completed for this MCP client (replay)", "content": { "application/json": { "schema": { @@ -43890,11 +43886,100 @@ } } }, + "/api/mcp/client/{id}/reauthorize": { + "post": { + "operationId": "reauthorizeMCPClient", + "summary": "Reauthorize an MCP client", + "description": "Redoes the OAuth consent flow for an already-authorized OAuth-based MCP\nclient, without delete-and-recreate. The flow always runs against the\ncredentials currently stored on the client's OAuth config.\n\n- auth_type \"oauth\": serves both a standalone admin-triggered reauth\n (e.g. the upstream provider revoked the credential and the client sits\n in needs_reauth) and the follow-up to rotating oauth_config via\n PUT /api/mcp/client/{id} (which cascades every bound token to\n needs_reauth).\n- auth_type \"per_user_oauth\": repairs the retained admin discovery\n credential used for periodic tool-list refresh. Only allowed while\n that credential actually sits in needs_reauth (409 otherwise);\n end-user credentials are untouched either way.\n\nComplete the returned flow like any other admin OAuth flow: open\nauthorize_url in a browser, poll status_url until \"authorized\", then\nPOST complete_url.\n", + "tags": [ + "MCP", + "OAuth" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "MCP client ID", + "schema": { + "type": "string" + } + } + ], + "security": [ + { + "ManagementBearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Reauthorization flow initiated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthFlowInitiation" + } + } + } + }, + "400": { + "description": "Client is not an OAuth-based auth type (oauth, per_user_oauth), or has never completed initial OAuth authorization (use initiate-verification instead)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BifrostError" + } + } + } + }, + "404": { + "description": "MCP client not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BifrostError" + } + } + } + }, + "409": { + "description": "The admin discovery credential for this per_user_oauth client does not need repair or does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BifrostError" + } + } + } + }, + "503": { + "description": "OAuth provider not configured", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BifrostError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BifrostError" + } + } + } + } + } + } + }, "/api/mcp/client/{id}/verify-headers": { "post": { "operationId": "verifyMCPClientHeaders", "summary": "Verify a pending per-user-headers MCP client", - "description": "Completes the one-time admin verification for an MCP client sitting in\npending_verification state with auth_type \"per_user_headers\" (declared\nvia config.json). The admin supplies sample values for every declared\nper_user_header_keys entry; Bifrost opens an upstream connection with\nthem, discovers the tool list, persists it, and transitions the client\nto connected. The sample values are discarded — each end-user submits\ntheir own values at runtime. Synchronous; no browser flow.\n", + "description": "Completes the admin verification for an MCP client with auth_type\n\"per_user_headers\". The admin supplies sample values for every declared\nper_user_header_keys entry; Bifrost opens an upstream connection with\nthem, discovers the tool list, persists it, and transitions the client\nto connected. The sample values are retained as the admin discovery\ncredential the periodic tool syncer uses to refresh the tool list; each\nend-user still submits their own values at runtime. Synchronous; no\nbrowser flow.\n\nServes two situations: the one-time bootstrap verification for a client\nsitting in pending_verification (declared via config.json), and a\nvoluntary refresh of an already-verified client's retained admin\ndiscovery credential — resubmitting sample values always re-runs\nverification and upserts the credential back to active, whether or not\nit currently needs repair (mirrors POST /reauthorize for OAuth-based\nclients).\n", "tags": [ "MCP" ], @@ -43924,7 +44009,7 @@ "additionalProperties": { "type": "string" }, - "description": "Sample value for every header name declared in the client's\nper_user_header_keys. Used once for the verification\nconnection, then discarded — never persisted.\n" + "description": "Sample value for every header name declared in the client's\nper_user_header_keys. Used for the verification connection,\nthen retained as the admin discovery credential for periodic\ntool-list refresh. Never used for end-user traffic and never\nsurfaced on /api/mcp/sessions.\n" } } } @@ -43982,16 +44067,6 @@ } } }, - "409": { - "description": "Client has already been verified (tools discovered); delete and recreate to re-verify", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BifrostError" - } - } - } - }, "422": { "description": "Upstream verification failed with the supplied header values", "content": { @@ -44018,7 +44093,7 @@ "/api/mcp/sessions": { "get": { "summary": "List MCP sessions", - "description": "Returns every per-user MCP authentication artifact visible to the caller —\nOAuth tokens, header credentials, and pending submission / consent flows.\n\nRow visibility is scoped to the caller's identity (Virtual Key, signed-in\nuser, or asserted session ID). Server-level `headers` / `oauth` clients\nare not surfaced here; their credentials live on the MCP client config.\n\nWhen both a credential and a pending flow exist for the same\n`(identity, mcp_client)` binding, the credential is returned and the\nflow is suppressed to avoid duplicate entries.\n", + "description": "Returns every per-user MCP authentication artifact visible to the caller:\nOAuth tokens, header credentials, and pending submission / consent flows.\n\nRow visibility is scoped to the caller's identity (Virtual Key, signed-in\nuser, or asserted session ID). Server-level `headers` / `oauth` clients\nare not surfaced here; their credentials live on the MCP client config.\nAdmin discovery credentials (the retained bootstrap credential Bifrost\nuses for periodic tool-list refresh on per-user clients) never appear\nhere either; only user-, vk-, and session-keyed rows are listed.\n\nWhen both a credential and a pending flow exist for the same\n`(identity, mcp_client)` binding, the credential is returned and the\nflow is suppressed to avoid duplicate entries.\n", "operationId": "listMcpSessions", "tags": [ "MCP" @@ -44713,7 +44788,7 @@ "delete": { "operationId": "revokeOAuthConfig", "summary": "Revoke OAuth config", - "description": "Revokes a server-level OAuth configuration and its associated access token.\nAfter revocation, the MCP client will no longer be able to use this OAuth token.\n", + "description": "Revokes a server-level OAuth configuration and its associated access token.\nAfter revocation, the MCP client will no longer be able to use this OAuth token.\nRevocation is not terminal for the client: an admin can restore access by\nredoing consent via POST /api/mcp/client/{id}/reauthorize, which runs\nagainst the credentials currently stored on the client's OAuth config.\n", "tags": [ "OAuth" ], @@ -79405,6 +79480,10 @@ "type": "integer", "description": "Timeout for MCP tool execution in seconds" }, + "mcp_tool_sync_interval": { + "type": "integer", + "description": "Global tool-list sync interval in minutes for MCP clients. Applies to clients whose per-client tool_sync_interval is 0 or omitted; a per-client positive value overrides it and a per-client negative value disables syncing for that client. When 0 or unset, a 10-minute default applies.\n" + }, "mcp_code_mode_binding_level": { "type": "string", "description": "Binding level for MCP code mode" @@ -79731,6 +79810,10 @@ "type": "integer", "description": "Timeout for MCP tool execution in seconds" }, + "mcp_tool_sync_interval": { + "type": "integer", + "description": "Global tool-list sync interval in minutes for MCP clients. Applies to clients whose per-client tool_sync_interval is 0 or omitted; a per-client positive value overrides it and a per-client negative value disables syncing for that client. When 0 or unset, a 10-minute default applies.\n" + }, "mcp_code_mode_binding_level": { "type": "string", "description": "Binding level for MCP code mode" @@ -81280,9 +81363,10 @@ "error", "pending_tools", "pending_verification", + "needs_reauth", "disabled" ], - "description": "Connection state of an MCP client:\n- connected: Client is connected and ready to use\n- disconnected: Client is not connected (will be auto-recovered by health monitor)\n- error: Client is in an unrecoverable error state\n- pending_tools: Connected but tools not yet populated (per-user OAuth clients)\n- pending_verification: Declared (typically via config.json) but the one-time\n admin verification has not been completed yet. Complete it via\n POST /api/mcp/client/{id}/initiate-verification (auth_type oauth /\n per_user_oauth) or POST /api/mcp/client/{id}/verify-headers\n (auth_type per_user_headers).\n- disabled: Client has been intentionally disabled; no connection or workers are active\n" + "description": "Connection state of an MCP client:\n- connected: Client is connected and ready to use\n- disconnected: Client is not connected (will be auto-recovered by health monitor)\n- error: Client is in an unrecoverable error state\n- pending_tools: Connected but tools not yet populated (per-user clients)\n- pending_verification: Declared (typically via config.json) but the one-time\n admin verification has not been completed yet. Complete it via\n POST /api/mcp/client/{id}/initiate-verification (auth_type oauth /\n per_user_oauth) or POST /api/mcp/client/{id}/verify-headers\n (auth_type per_user_headers).\n- needs_reauth: Setup completed at least once, but a credential an admin is\n responsible for has permanently died and needs a human to repair it.\n For shared OAuth clients the connection credential itself was\n rejected/expired with no silent recovery; the state is sticky (the health\n monitor will not clobber it) until an admin runs\n POST /api/mcp/client/{id}/reauthorize. For per-user clients this is a\n response-only projection meaning the retained admin discovery credential\n needs repair; end-user credentials and tool calls keep working, only\n periodic tool-list refresh pauses. Repair via reauthorize\n (per_user_oauth) or verify-headers with fresh sample values\n (per_user_headers).\n- disabled: Client has been intentionally disabled; no connection or workers are active\n" }, "vk_configs": { "type": "array", @@ -81526,7 +81610,7 @@ }, "OAuthConfigRequest": { "type": "object", - "description": "OAuth configuration for MCP client creation", + "description": "OAuth configuration for an MCP client. Used both when creating a client\n(initiates the OAuth flow) and when updating one (rotates the stored\nOAuth config in place; see MCPClientUpdateRequest.oauth_config).\n", "properties": { "client_id": { "allOf": [ @@ -81602,6 +81686,10 @@ "type": "string" }, "description": "OAuth scopes requested. Optional - can be discovered from server_url if not provided.\nExample: [\"read\", \"write\"]\n" + }, + "resource": { + "type": "string", + "description": "Resource indicator (RFC 8707) sent on authorization and token requests.\nOptional - identifies the protected MCP resource the token is intended\nfor, when the upstream provider supports resource indicators.\n" } } }, diff --git a/docs/openapi/openapi.yaml b/docs/openapi/openapi.yaml index f76de9381bb..11f04c19782 100644 --- a/docs/openapi/openapi.yaml +++ b/docs/openapi/openapi.yaml @@ -797,6 +797,8 @@ paths: $ref: './paths/management/mcp.yaml#/client-complete-oauth' /api/mcp/client/{id}/initiate-verification: $ref: './paths/management/mcp.yaml#/client-initiate-verification' + /api/mcp/client/{id}/reauthorize: + $ref: './paths/management/mcp.yaml#/client-reauthorize' /api/mcp/client/{id}/verify-headers: $ref: './paths/management/mcp.yaml#/client-verify-headers' diff --git a/docs/openapi/paths/management/mcp.yaml b/docs/openapi/paths/management/mcp.yaml index 68bd57c5123..0705eb0de50 100644 --- a/docs/openapi/paths/management/mcp.yaml +++ b/docs/openapi/paths/management/mcp.yaml @@ -124,8 +124,14 @@ clients: in: query description: | Comma-separated runtime connection states to include (OR semantics), - resolved against live engine state. `connected` matches clients the engine - currently reports as connected; `disconnected` matches everything else. + resolved against live engine state. Only `connected` and + `disconnected` are meaningful filter values: `connected` matches + clients the engine currently reports as connected; `disconnected` + matches everything else (error, pending states, needs_reauth, + disabled, not present in the engine). Selecting both, or neither, + applies no state filter. Note the response-only needs_reauth + projection on per-user clients happens after filtering, so such + clients still match `connected`. schema: type: string example: connected @@ -169,7 +175,10 @@ client: summary: Add MCP client description: | Adds a new MCP client with the specified configuration. - Note: tool_pricing is not available when creating a new client as tools are fetched after client creation. + Note: tool_pricing is not available when creating a new client; tool + pricing can only be set once the tool list is known. For shared-connection + clients tools are fetched after client creation; for per-user auth types + they are discovered during the create/verify flow itself. tags: - MCP requestBody: @@ -204,8 +213,13 @@ client-by-id: operationId: editMCPClient summary: Edit MCP client description: | - Updates an existing MCP client's configuration. + Updates an existing MCP client's configuration. All fields are optional + (PATCH semantics); connection_type, auth_type, connection_string, + stdio_config, and oauth_config_id are immutable after creation. Unlike client creation, tool_pricing can be included to set per-tool execution costs since tools are already fetched. + For OAuth-based clients, providing oauth_config rotates the stored OAuth + configuration in place and flips every bound token to needs_reauth when + a field actually changes (see MCPClientUpdateRequest.oauth_config). Optionally provide vk_configs to manage which virtual keys have access to this MCP server and with which tools. When provided, this fully replaces all existing VK assignments in a single atomic transaction. Set disabled: true to shut down the client's connection and workers without removing it. Set disabled: false to reconnect a previously disabled client. tags: @@ -301,10 +315,25 @@ client-complete-oauth: operationId: completeMCPClientOAuth summary: Complete MCP client OAuth flow description: | - Completes the OAuth flow for an MCP client after the user has authorized the request. - This endpoint should be called after the OAuth provider redirects back to the callback endpoint - and the OAuth token has been stored. It retrieves the pending MCP client configuration and - establishes the connection with the OAuth-provided credentials. + Completes an OAuth flow for an MCP client after the admin has authorized + the request upstream. Call it once the flow's status_url reports + "authorized". It serves every admin-side OAuth completion with one + endpoint: + + - Create-time and config.json-bootstrap flows: retrieves the pending MCP + client configuration and establishes the connection with the + OAuth-provided credentials (per_user_oauth clients instead verify with + the admin token, discover tools, and retain the token as the admin + discovery credential). + - Reauthorize flows (started via POST /api/mcp/client/{id}/reauthorize): + for shared "oauth" clients, reconnects the client with the fresh + credential; for per_user_oauth clients, verifies the fresh admin token + upstream, re-discovers tools, and promotes it to the retained admin + discovery credential. + + Replays are rejected with 409: hitting the endpoint again after the flow + already completed (no pending configuration and no freshly-written + token) returns "OAuth flow has already been completed". tags: - MCP - OAuth @@ -312,24 +341,30 @@ client-complete-oauth: - name: id in: path required: true - description: MCP client ID + description: | + The oauth_config_id of the flow being completed (as returned in the + initiation response's oauth_config_id / complete_url), not the MCP + client ID. schema: type: string security: - ManagementBearerAuth: [] responses: '200': - description: MCP client connected successfully with OAuth + description: MCP client connected (or re-authorized) successfully with OAuth content: application/json: schema: $ref: '../../schemas/management/common.yaml#/SuccessResponse' '400': - description: OAuth not authorized yet or MCP client not found in pending OAuth clients + description: OAuth flow not authorized yet, or the OAuth config does not belong to an OAuth-based MCP client $ref: '../../openapi.yaml#/components/responses/BadRequest' '404': - description: MCP client not found in pending OAuth clients or OAuth config not found + description: OAuth config not found, or no MCP client is linked to this OAuth flow $ref: '../../openapi.yaml#/components/responses/NotFound' + '409': + description: OAuth flow has already been completed for this MCP client (replay) + $ref: '../../openapi.yaml#/components/responses/Conflict' '500': $ref: '../../openapi.yaml#/components/responses/InternalError' @@ -376,18 +411,86 @@ client-initiate-verification: '500': $ref: '../../openapi.yaml#/components/responses/InternalError' +client-reauthorize: + post: + operationId: reauthorizeMCPClient + summary: Reauthorize an MCP client + description: | + Redoes the OAuth consent flow for an already-authorized OAuth-based MCP + client, without delete-and-recreate. The flow always runs against the + credentials currently stored on the client's OAuth config. + + - auth_type "oauth": serves both a standalone admin-triggered reauth + (e.g. the upstream provider revoked the credential and the client sits + in needs_reauth) and the follow-up to rotating oauth_config via + PUT /api/mcp/client/{id} (which cascades every bound token to + needs_reauth). + - auth_type "per_user_oauth": repairs the retained admin discovery + credential used for periodic tool-list refresh. Only allowed while + that credential actually sits in needs_reauth (409 otherwise); + end-user credentials are untouched either way. + + Complete the returned flow like any other admin OAuth flow: open + authorize_url in a browser, poll status_url until "authorized", then + POST complete_url. + tags: + - MCP + - OAuth + parameters: + - name: id + in: path + required: true + description: MCP client ID + schema: + type: string + security: + - ManagementBearerAuth: [] + responses: + '200': + description: Reauthorization flow initiated + content: + application/json: + schema: + $ref: '../../schemas/management/oauth.yaml#/OAuthFlowInitiation' + '400': + description: Client is not an OAuth-based auth type (oauth, per_user_oauth), or has never completed initial OAuth authorization (use initiate-verification instead) + $ref: '../../openapi.yaml#/components/responses/BadRequest' + '404': + description: MCP client not found + $ref: '../../openapi.yaml#/components/responses/NotFound' + '409': + description: The admin discovery credential for this per_user_oauth client does not need repair or does not exist + $ref: '../../openapi.yaml#/components/responses/Conflict' + '503': + description: OAuth provider not configured + content: + application/json: + schema: + $ref: '../../schemas/inference/common.yaml#/BifrostError' + '500': + $ref: '../../openapi.yaml#/components/responses/InternalError' + client-verify-headers: post: operationId: verifyMCPClientHeaders summary: Verify a pending per-user-headers MCP client description: | - Completes the one-time admin verification for an MCP client sitting in - pending_verification state with auth_type "per_user_headers" (declared - via config.json). The admin supplies sample values for every declared + Completes the admin verification for an MCP client with auth_type + "per_user_headers". The admin supplies sample values for every declared per_user_header_keys entry; Bifrost opens an upstream connection with them, discovers the tool list, persists it, and transitions the client - to connected. The sample values are discarded — each end-user submits - their own values at runtime. Synchronous; no browser flow. + to connected. The sample values are retained as the admin discovery + credential the periodic tool syncer uses to refresh the tool list; each + end-user still submits their own values at runtime. Synchronous; no + browser flow. + + Serves two situations: the one-time bootstrap verification for a client + sitting in pending_verification (declared via config.json), and a + voluntary refresh of an already-verified client's retained admin + discovery credential — resubmitting sample values always re-runs + verification and upserts the credential back to active, whether or not + it currently needs repair (mirrors POST /reauthorize for OAuth-based + clients). tags: - MCP parameters: @@ -411,8 +514,10 @@ client-verify-headers: type: string description: | Sample value for every header name declared in the client's - per_user_header_keys. Used once for the verification - connection, then discarded — never persisted. + per_user_header_keys. Used for the verification connection, + then retained as the admin discovery credential for periodic + tool-list refresh. Never used for end-user traffic and never + surfaced on /api/mcp/sessions. security: - ManagementBearerAuth: [] responses: @@ -436,9 +541,6 @@ client-verify-headers: $ref: '../../openapi.yaml#/components/responses/BadRequest' '404': $ref: '../../openapi.yaml#/components/responses/NotFound' - '409': - description: Client has already been verified (tools discovered); delete and recreate to re-verify - $ref: '../../openapi.yaml#/components/responses/Conflict' '422': description: Upstream verification failed with the supplied header values content: @@ -454,12 +556,15 @@ sessions: get: summary: List MCP sessions description: | - Returns every per-user MCP authentication artifact visible to the caller — + Returns every per-user MCP authentication artifact visible to the caller: OAuth tokens, header credentials, and pending submission / consent flows. Row visibility is scoped to the caller's identity (Virtual Key, signed-in user, or asserted session ID). Server-level `headers` / `oauth` clients are not surfaced here; their credentials live on the MCP client config. + Admin discovery credentials (the retained bootstrap credential Bifrost + uses for periodic tool-list refresh on per-user clients) never appear + here either; only user-, vk-, and session-keyed rows are listed. When both a credential and a pending flow exist for the same `(identity, mcp_client)` binding, the credential is returned and the diff --git a/docs/openapi/paths/management/oauth.yaml b/docs/openapi/paths/management/oauth.yaml index b78965d3f35..509efb19644 100644 --- a/docs/openapi/paths/management/oauth.yaml +++ b/docs/openapi/paths/management/oauth.yaml @@ -96,6 +96,9 @@ oauth-config-by-id: description: | Revokes a server-level OAuth configuration and its associated access token. After revocation, the MCP client will no longer be able to use this OAuth token. + Revocation is not terminal for the client: an admin can restore access by + redoing consent via POST /api/mcp/client/{id}/reauthorize, which runs + against the credentials currently stored on the client's OAuth config. tags: - OAuth parameters: @@ -217,8 +220,9 @@ per-user-oauth-flow-start: '500': $ref: '../../openapi.yaml#/components/responses/InternalError' -# ─── Removed: OAuth-server endpoints (RFC 7591/8414 surface) ──────────────── -# Bifrost is not an OAuth Authorization Server. Upstream OAuth happens via -# the per-user-oauth-flow-* endpoints above. - -# Legacy block intentionally removed: +# The endpoints in this file cover upstream MCP OAuth only, Bifrost acting +# as an OAuth *client* against external MCP servers (via the +# per-user-oauth-flow-* endpoints above and the /api/mcp/client/* flows). +# The inbound surface where Bifrost acts as an OAuth Authorization Server +# for /mcp callers (/.well-known/*, /oauth2/*) is a separate feature and is +# not documented in this file. diff --git a/docs/openapi/schemas/management/config.yaml b/docs/openapi/schemas/management/config.yaml index 02d552d1fed..5fb38913e67 100644 --- a/docs/openapi/schemas/management/config.yaml +++ b/docs/openapi/schemas/management/config.yaml @@ -78,6 +78,14 @@ ClientConfig: mcp_tool_execution_timeout: type: integer description: Timeout for MCP tool execution in seconds + mcp_tool_sync_interval: + type: integer + description: > + Global tool-list sync interval in minutes for MCP clients. Applies to + clients whose per-client tool_sync_interval is 0 or omitted; a + per-client positive value overrides it and a per-client negative value + disables syncing for that client. When 0 or unset, a 10-minute default + applies. mcp_code_mode_binding_level: type: string description: Binding level for MCP code mode diff --git a/docs/openapi/schemas/management/mcp.yaml b/docs/openapi/schemas/management/mcp.yaml index 761bcc1abf8..6a9e74056e9 100644 --- a/docs/openapi/schemas/management/mcp.yaml +++ b/docs/openapi/schemas/management/mcp.yaml @@ -18,18 +18,29 @@ MCPConnectionType: MCPConnectionState: type: string - enum: [connected, disconnected, error, pending_tools, pending_verification, disabled] + enum: [connected, disconnected, error, pending_tools, pending_verification, needs_reauth, disabled] description: | Connection state of an MCP client: - connected: Client is connected and ready to use - disconnected: Client is not connected (will be auto-recovered by health monitor) - error: Client is in an unrecoverable error state - - pending_tools: Connected but tools not yet populated (per-user OAuth clients) + - pending_tools: Connected but tools not yet populated (per-user clients) - pending_verification: Declared (typically via config.json) but the one-time admin verification has not been completed yet. Complete it via POST /api/mcp/client/{id}/initiate-verification (auth_type oauth / per_user_oauth) or POST /api/mcp/client/{id}/verify-headers (auth_type per_user_headers). + - needs_reauth: Setup completed at least once, but a credential an admin is + responsible for has permanently died and needs a human to repair it. + For shared OAuth clients the connection credential itself was + rejected/expired with no silent recovery; the state is sticky (the health + monitor will not clobber it) until an admin runs + POST /api/mcp/client/{id}/reauthorize. For per-user clients this is a + response-only projection meaning the retained admin discovery credential + needs repair; end-user credentials and tool calls keep working, only + periodic tool-list refresh pauses. Repair via reauthorize + (per_user_oauth) or verify-headers with fresh sample values + (per_user_headers). - disabled: Client has been intentionally disabled; no connection or workers are active MCPStdioConfig: @@ -109,6 +120,28 @@ MCPClientCreateRequestBase: Whether the MCP server supports ping for health checks. If true, uses lightweight ping method for health checks. If false, uses listTools method for health checks instead. + tool_sync_interval: + type: integer + description: | + Per-client tool-list sync interval in minutes. 0 (or omitted) falls back + to the global mcp_tool_sync_interval client config; a negative value + disables periodic tool sync for this client. + tool_execution_timeout: + type: integer + minimum: 0 + description: | + Per-client tool execution timeout in seconds. 0 (or omitted) falls back + to the global mcp_tool_execution_timeout client config. + allowed_extra_headers: + type: array + items: + type: string + description: | + Allowlist of request-level headers callers may forward to this MCP + server at execution time. + ["*"] => any header may be forwarded + [] or omitted => no extra headers are forwarded + ["header1", "header2"] => only the specified headers connection_type: $ref: '#/MCPConnectionType' auth_type: @@ -193,9 +226,11 @@ MCPClientCreateRequestBase: description: | Used only at create time when `auth_type` is `per_user_headers`. A sample set of header values the admin supplies so Bifrost can run a one-time - upstream verify and discover the tool list. Discarded after the create - call — not persisted. Mirrors how the admin's temp OAuth token is used - for `per_user_oauth` setup. + upstream verify and discover the tool list. Not persisted by the create + call; each end-user submits their own values at runtime. To have Bifrost + retain an admin discovery credential for periodic tool-list refresh, + use POST /api/mcp/client/{id}/verify-headers, which stores the sample + values as that credential. MCPClientCreateRequestHTTP: allOf: @@ -241,55 +276,74 @@ MCPClientCreateRequestSTDIO: MCPClientUpdateRequest: type: object - description: MCP client configuration for updating an existing client (includes tool_pricing) - allOf: - - if: - required: [auth_type] - properties: - auth_type: - const: per_user_headers - then: - required: [per_user_header_keys] - properties: - per_user_header_keys: - type: array - minItems: 1 - items: - type: string - minLength: 1 + description: | + MCP client configuration for updating an existing client (includes tool_pricing). + All fields are optional; omitting a field retains its existing value (PATCH + semantics). Immutable fields (connection_type, auth_type, connection_string, + stdio_config, oauth_config_id) are not accepted here; they cannot be changed + after creation. properties: - client_id: - type: string - description: Unique identifier for the MCP client name: type: string description: Display name for the MCP client is_code_mode_client: type: boolean description: Whether this client is available in code mode - connection_type: - $ref: '#/MCPConnectionType' - connection_string: - type: string - description: HTTP or SSE URL (required for HTTP or SSE connections) - stdio_config: - $ref: '#/MCPStdioConfig' - auth_type: - $ref: '#/MCPAuthType' - description: Authentication type for the MCP connection - oauth_config_id: - type: string + is_ping_available: + type: boolean description: | - OAuth config ID for OAuth authentication. - References the oauth_configs table. - Only relevant when auth_type is "oauth". + Whether the MCP server supports ping for health checks. + If true, uses lightweight ping method for health checks. + If false, uses listTools method for health checks instead. + tool_sync_interval: + type: integer + description: | + Per-client tool-list sync interval in minutes. 0 falls back to the + global mcp_tool_sync_interval client config; a negative value disables + periodic tool sync for this client. + tool_execution_timeout: + type: integer + minimum: 0 + description: | + Per-client tool execution timeout in seconds. 0 falls back to the + global mcp_tool_execution_timeout client config. headers: type: object additionalProperties: type: string description: | Custom headers to include in requests. - Only used when auth_type is "headers". + Only used when auth_type is "headers". Supports env./vault. references. + Values echoed back redacted from a GET response are recognized and + preserve the stored value, so a fetch-modify-put round trip is safe. + allowed_extra_headers: + type: array + items: + type: string + description: | + Allowlist of request-level headers callers may forward to this MCP + server at execution time. + ["*"] => any header may be forwarded + [] => no extra headers are forwarded + ["header1", "header2"] => only the specified headers + oauth_config: + $ref: '../../schemas/management/oauth.yaml#/OAuthConfigRequest' + description: | + Rotates the stored OAuth configuration in place. Only accepted for + auth_type "oauth" or "per_user_oauth" (400 otherwise). Any provided + field replaces the stored value; no new config row is created and no + re-discovery or re-registration runs. Unset fields preserve stored + values: client_id/client_secret follow the SecretVar masked-placeholder + convention (send back the redacted placeholder from a GET response to + keep the stored secret), while empty strings / empty arrays on the + remaining fields mean "not provided". + + When any field actually changes, every token bound to the OAuth config + is flipped to needs_reauth regardless of auth mode, shared and + per-user sessions alike must re-authenticate. A round trip that + resolves to the stored values is a no-op and does not cascade. + Rotation cannot run while the client is (or is being) disabled + (400); send those as two separate requests. tools_to_execute: type: array items: @@ -331,9 +385,12 @@ MCPClientUpdateRequest: items: type: string description: | - For `per_user_headers` clients only. Updating this list flips every existing - active per-user credential row to `needs_update`; callers will be sent back to - the submission form on their next tool call to satisfy the new schema. + For `per_user_headers` clients only. Cannot be set to an empty list. + Updating this list flips every existing active per-user credential row + to `needs_update`; callers will be sent back to the submission form on + their next tool call to satisfy the new schema. The retained admin + discovery credential is flipped the same way; repair it by calling + POST /api/mcp/client/{id}/verify-headers with fresh sample values. disabled: type: boolean default: false diff --git a/docs/openapi/schemas/management/oauth.yaml b/docs/openapi/schemas/management/oauth.yaml index 6a9428ea5c8..bcf7e1b57d1 100644 --- a/docs/openapi/schemas/management/oauth.yaml +++ b/docs/openapi/schemas/management/oauth.yaml @@ -13,7 +13,10 @@ MCPAuthType: OAuthConfigRequest: type: object - description: OAuth configuration for MCP client creation + description: | + OAuth configuration for an MCP client. Used both when creating a client + (initiates the OAuth flow) and when updating one (rotates the stored + OAuth config in place; see MCPClientUpdateRequest.oauth_config). properties: client_id: allOf: @@ -47,6 +50,12 @@ OAuthConfigRequest: description: | OAuth scopes requested. Optional - can be discovered from server_url if not provided. Example: ["read", "write"] + resource: + type: string + description: | + Resource indicator (RFC 8707) sent on authorization and token requests. + Optional - identifies the protected MCP resource the token is intended + for, when the upstream provider supports resource indicators. OAuthFlowInitiation: type: object diff --git a/transports/bifrost-http/handlers/mcp.go b/transports/bifrost-http/handlers/mcp.go index 2582025ec93..9fc92d28ef6 100644 --- a/transports/bifrost-http/handlers/mcp.go +++ b/transports/bifrost-http/handlers/mcp.go @@ -1205,6 +1205,18 @@ func (h *MCPHandler) addMCPClient(ctx *fasthttp.RequestCtx) { return } + // tool_execution_timeout: 0 (unset) means "use global from + // tool_manager_config", matching TableMCPClient's own column semantics — + // req.ToolExecutionTimeout is a plain int (embedded from TableMCPClient), + // not a pointer, so 0 can't be distinguished from "not sent"; same + // convention tool_sync_interval already uses on this create path. + // Computed once here and reused across every creation branch below. + if req.ToolExecutionTimeout < 0 { + SendError(ctx, fasthttp.StatusBadRequest, "tool_execution_timeout must not be negative") + return + } + resolvedToolExecutionTimeout := time.Duration(req.ToolExecutionTimeout) * time.Second + // Handle per-user headers: admin declares the required key names (schema) // AND supplies a sample set of values inline so the server can verify // upstream + discover tools in a single round-trip. Mirrors the per-user @@ -1269,6 +1281,7 @@ func (h *MCPHandler) addMCPClient(ctx *fasthttp.RequestCtx) { IsCodeModeClient: req.IsCodeModeClient, IsPingAvailable: &isPingAvailable, ToolSyncInterval: toolSyncInterval, + ToolExecutionTimeout: resolvedToolExecutionTimeout, ConnectionType: schemas.MCPConnectionType(req.ConnectionType), ConnectionString: req.ConnectionString, StdioConfig: req.StdioConfig, @@ -1371,6 +1384,7 @@ func (h *MCPHandler) addMCPClient(ctx *fasthttp.RequestCtx) { IsCodeModeClient: req.IsCodeModeClient, IsPingAvailable: &isPingAvailable, ToolSyncInterval: toolSyncInterval, + ToolExecutionTimeout: resolvedToolExecutionTimeout, ConnectionType: schemas.MCPConnectionType(req.ConnectionType), ConnectionString: req.ConnectionString, StdioConfig: req.StdioConfig, @@ -1464,6 +1478,7 @@ func (h *MCPHandler) addMCPClient(ctx *fasthttp.RequestCtx) { IsCodeModeClient: req.IsCodeModeClient, IsPingAvailable: req.IsPingAvailable, ToolSyncInterval: toolSyncInterval, + ToolExecutionTimeout: resolvedToolExecutionTimeout, ConnectionType: schemas.MCPConnectionType(req.ConnectionType), ConnectionString: req.ConnectionString, StdioConfig: req.StdioConfig, @@ -1538,6 +1553,7 @@ func (h *MCPHandler) addMCPClient(ctx *fasthttp.RequestCtx) { OauthConfigID: req.OauthConfigID, IsPingAvailable: req.IsPingAvailable, ToolSyncInterval: toolSyncInterval, + ToolExecutionTimeout: resolvedToolExecutionTimeout, ToolPricing: req.ToolPricing, AllowOnAllVirtualKeys: req.AllowOnAllVirtualKeys, }