diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index acbb2156..ba94321a 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -65,25 +65,42 @@ export function createRelayMcpServer(options: McpServerOptions): McpServer { session.wsBridge = null; session.subscriptions = null; } + if (shouldResetBridge) { + session.wsInitAttempted = false; + } // When an agent token is set, initialize the WebSocket bridge. - if (nextAgentToken && !session.wsBridge) { - const subscriptions = new SubscriptionManager(); - const wsClient = new WsClient({ - token: nextAgentToken, - baseUrl: options.baseUrl, - }); - const wsBridge = new WsBridge( - wsClient, - subscriptions, - (uri: string) => { - mcpServer.server.sendResourceUpdated({ uri }).catch(() => { - // Silently ignore notification failures - }); - }, - ); - wsBridge.start(); - Object.assign(session, partial, { wsBridge, subscriptions }); + if (nextAgentToken && !session.wsBridge && !session.wsInitAttempted) { + try { + const subscriptions = new SubscriptionManager(); + const wsClient = new WsClient({ + token: nextAgentToken, + baseUrl: options.baseUrl, + }); + const wsBridge = new WsBridge( + wsClient, + subscriptions, + (uri: string) => { + mcpServer.server.sendResourceUpdated({ uri }).catch(() => { + // Silently ignore notification failures + }); + }, + ); + wsBridge.start(); + Object.assign(session, partial, { + wsBridge, + subscriptions, + wsInitAttempted: true, + }); + } catch { + // In non-WS runtimes (e.g. some test environments), keep session usable + // without real-time resource updates. + Object.assign(session, partial, { + wsBridge: null, + subscriptions: null, + wsInitAttempted: true, + }); + } telemetry.capture('relaycast_mcp_session_authenticated', { source_surface: 'mcp', agent_name: nextAgentName, diff --git a/packages/mcp/src/types.ts b/packages/mcp/src/types.ts index 7cf67182..2aeb3eca 100644 --- a/packages/mcp/src/types.ts +++ b/packages/mcp/src/types.ts @@ -7,8 +7,16 @@ export interface SessionState { agentName: string | null; wsBridge: WsBridge | null; subscriptions: SubscriptionManager | null; + wsInitAttempted: boolean; } export function createInitialSession(workspaceKey: string | null = null): SessionState { - return { workspaceKey, agentToken: null, agentName: null, wsBridge: null, subscriptions: null }; + return { + workspaceKey, + agentToken: null, + agentName: null, + wsBridge: null, + subscriptions: null, + wsInitAttempted: false, + }; } diff --git a/smithery.yaml b/smithery.yaml new file mode 100644 index 00000000..4d9cd335 --- /dev/null +++ b/smithery.yaml @@ -0,0 +1,25 @@ +startCommand: + type: stdio + configSchema: + type: object + additionalProperties: false + properties: + relayApiKey: + type: string + description: Workspace API key (`rk_live_...`) used to pre-authenticate the MCP session. + relayBaseUrl: + type: string + description: Override API base URL for self-hosted Relaycast deployments. + default: https://api.relaycast.dev + commandFunction: |- + (config) => { + const env = {}; + if (config.relayApiKey) env.RELAY_API_KEY = config.relayApiKey; + if (config.relayBaseUrl) env.RELAY_BASE_URL = config.relayBaseUrl; + + return { + command: "npx", + args: ["-y", "tsx", "packages/mcp/src/stdio.ts"], + env + }; + }