From 41440280afc72273d025d36ca680c538719f103a Mon Sep 17 00:00:00 2001 From: dcguim Date: Tue, 7 Apr 2026 10:49:10 +0200 Subject: [PATCH 1/2] Expose & Set up config client name in stateless mode --- packages/sunpeak/src/mcp/production-server.ts | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/sunpeak/src/mcp/production-server.ts b/packages/sunpeak/src/mcp/production-server.ts index c4f3298..8b1b0fa 100644 --- a/packages/sunpeak/src/mcp/production-server.ts +++ b/packages/sunpeak/src/mcp/production-server.ts @@ -143,6 +143,17 @@ export interface ProductionServerConfig { * where holding open SSE connections is unreliable. Defaults to `true`. */ enableJsonResponse?: boolean; + /** + * Override the client name used for domain resolution (e.g. `'chatgpt'`). + * + * Normally, client name is detected from the MCP initialization handshake. + * In stateless/serverless environments where each request creates a fresh + * server instance, the client name won't be available for non-initialize + * requests. Set this to ensure proper domain resolution for resources. + * + * Common values: `'chatgpt'`, `'openai-mcp'`, `'claude'` + */ + clientName?: string; } /** @@ -194,6 +205,7 @@ export function createProductionMcpServer(config: ProductionServerConfig): McpSe tools, resources, serverUrl, + clientName: configClientName, } = config; const mcpServer = new McpServer( @@ -218,9 +230,11 @@ export function createProductionMcpServer(config: ProductionServerConfig): McpSe // Read callbacks close over this variable to resolve host-specific domain maps. // Also update listing-level _meta on all resources so that `resources/list` // returns the resolved domain (hosts like ChatGPT check domain from the listing). - let clientName: string | undefined; + // In stateless/serverless mode, use configClientName as the default since + // oninitialized won't fire for non-initialize requests. + let clientName: string | undefined = configClientName; mcpServer.server.oninitialized = () => { - clientName = mcpServer.server.getClientVersion()?.name; + clientName = mcpServer.server.getClientVersion()?.name ?? configClientName; for (const handle of resourceHandles) { const currentMeta = handle.metadata?._meta as Record | undefined; @@ -248,6 +262,10 @@ export function createProductionMcpServer(config: ProductionServerConfig): McpSe let toolCount = 0; for (const tool of tools) { + // Get the resource for this tool (if any) for use in callback + const toolResourceName = tool.tool.resource; + const toolResource = toolResourceName ? resourceByName.get(toolResourceName) : undefined; + // Build the handler callback (shared by UI and plain tools) const makeCallback = () => { return async ( @@ -269,6 +287,26 @@ export function createProductionMcpServer(config: ProductionServerConfig): McpSe if (typeof result === 'string') { return { content: [{ type: 'text' as const, text: result }] }; } + + // For tools with resources, ensure the result includes the resource reference + // This is critical for ChatGPT to know which UI to render + if (toolResource && 'structuredContent' in result) { + const existingMeta = (result as Record)._meta as Record | undefined; + const existingUi = existingMeta?.ui as Record | undefined; + + // Only add if not already present + if (!existingUi?.resourceUri) { + (result as Record)._meta = { + ...existingMeta, + ui: { + ...existingUi, + resourceUri: toolResource.uri, + }, + }; + log('info', `Added resourceUri to tool result: ${toolResource.uri}`); + } + } + return result; }; }; @@ -300,6 +338,14 @@ export function createProductionMcpServer(config: ProductionServerConfig): McpSe const readMeta = serverUrl ? injectDefaultDomain(resolved, clientName, serverUrl) : resolved; + // Debug logging for resource read + log('info', `ReadResource: ${res.name}`, { + clientName, + serverUrl, + hasDomain: !!(readMeta as Record)?.ui && + !!((readMeta as Record).ui as Record)?.domain, + domain: ((readMeta as Record)?.ui as Record)?.domain, + }); return { contents: [ { From 5978b16e8f11fe06cc0cf2f2f5db3ddb25be7585 Mon Sep 17 00:00:00 2001 From: dcguim Date: Tue, 7 Apr 2026 14:14:50 +0200 Subject: [PATCH 2/2] Remove unecessary logs and unecessary ui uri resource injection --- packages/sunpeak/src/mcp/production-server.ts | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/packages/sunpeak/src/mcp/production-server.ts b/packages/sunpeak/src/mcp/production-server.ts index 8b1b0fa..63b93f4 100644 --- a/packages/sunpeak/src/mcp/production-server.ts +++ b/packages/sunpeak/src/mcp/production-server.ts @@ -262,10 +262,6 @@ export function createProductionMcpServer(config: ProductionServerConfig): McpSe let toolCount = 0; for (const tool of tools) { - // Get the resource for this tool (if any) for use in callback - const toolResourceName = tool.tool.resource; - const toolResource = toolResourceName ? resourceByName.get(toolResourceName) : undefined; - // Build the handler callback (shared by UI and plain tools) const makeCallback = () => { return async ( @@ -288,25 +284,6 @@ export function createProductionMcpServer(config: ProductionServerConfig): McpSe return { content: [{ type: 'text' as const, text: result }] }; } - // For tools with resources, ensure the result includes the resource reference - // This is critical for ChatGPT to know which UI to render - if (toolResource && 'structuredContent' in result) { - const existingMeta = (result as Record)._meta as Record | undefined; - const existingUi = existingMeta?.ui as Record | undefined; - - // Only add if not already present - if (!existingUi?.resourceUri) { - (result as Record)._meta = { - ...existingMeta, - ui: { - ...existingUi, - resourceUri: toolResource.uri, - }, - }; - log('info', `Added resourceUri to tool result: ${toolResource.uri}`); - } - } - return result; }; }; @@ -338,14 +315,6 @@ export function createProductionMcpServer(config: ProductionServerConfig): McpSe const readMeta = serverUrl ? injectDefaultDomain(resolved, clientName, serverUrl) : resolved; - // Debug logging for resource read - log('info', `ReadResource: ${res.name}`, { - clientName, - serverUrl, - hasDomain: !!(readMeta as Record)?.ui && - !!((readMeta as Record).ui as Record)?.domain, - domain: ((readMeta as Record)?.ui as Record)?.domain, - }); return { contents: [ {