@@ -6,10 +6,12 @@ import { makeOAuthConsent } from './app';
66// `instanceof McpServer` check fails because the two `McpServer` classes are
77// distinct constructors.
88import { McpAgent } from 'agents/mcp' ;
9+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' ;
910import OAuthProvider from '@cloudflare/workers-oauth-provider' ;
1011import { ClientOptions } from '@imagekit/nodejs' ;
1112import { McpOptions } from '@imagekit/api-mcp/options' ;
1213import { initMcpServer , newMcpServer } from '@imagekit/api-mcp/server' ;
14+ import { configureLogger } from '@imagekit/api-mcp/logger' ;
1315import type { ExportedHandler } from '@cloudflare/workers-types' ;
1416
1517type MCPProps = {
@@ -58,19 +60,74 @@ const serverConfig: ServerConfig = {
5860 ] ,
5961} ;
6062
63+ // `newMcpServer` fetches MCP server instructions from the Stainless API. In a
64+ // Durable Object, that fetch happens inside `blockConcurrencyWhile`; if it
65+ // hangs the DO is reset, and if it rejects the same thing happens. Race
66+ // against a short timeout and catch any rejection so any failure mode lands
67+ // on a fallback server constructed without instructions (the `initialize`
68+ // response simply omits the `instructions` field, which is spec-allowed).
69+ const INSTRUCTIONS_FETCH_TIMEOUT_MS = 5000 ;
70+
71+ function fallbackMcpServer ( ) : McpServer {
72+ return new McpServer (
73+ { name : 'imagekit_nodejs_api' , version : '7.5.0' } ,
74+ { capabilities : { tools : { } , logging : { } } } ,
75+ ) ;
76+ }
77+
78+ async function buildMcpServer ( stainlessApiKey ?: string ) : Promise < McpServer > {
79+ let timeoutId : ReturnType < typeof setTimeout > | undefined ;
80+ try {
81+ const fetched = newMcpServer ( { stainlessApiKey } ) ;
82+ const timeout = new Promise < null > ( ( resolve ) => {
83+ timeoutId = setTimeout ( ( ) => resolve ( null ) , INSTRUCTIONS_FETCH_TIMEOUT_MS ) ;
84+ } ) ;
85+
86+ const result = await Promise . race ( [ fetched , timeout ] ) ;
87+
88+ if ( result != null ) {
89+ return result ;
90+ }
91+ } catch ( error ) {
92+ console . error ( 'Failed to build MCP server from upstream instructions; using fallback' , error ) ;
93+ } finally {
94+ if ( timeoutId != null ) {
95+ clearTimeout ( timeoutId ) ;
96+ }
97+ }
98+
99+ return fallbackMcpServer ( ) ;
100+ }
101+
61102export class MyMCP extends McpAgent < Env , unknown , MCPProps > {
62- server = newMcpServer ( { } ) ;
103+ #resolveServer! : ( server : McpServer ) => void ;
104+ #rejectServer! : ( error : unknown ) => void ;
105+ server : Promise < McpServer > = new Promise < McpServer > ( ( resolve , reject ) => {
106+ this . #resolveServer = resolve ;
107+ this . #rejectServer = reject ;
108+ } ) ;
63109
64110 async init ( ) {
65- if ( this . props == null ) {
66- throw new Error ( 'MCP props are not initialized' ) ;
67- }
111+ try {
112+ if ( this . props == null ) {
113+ throw new Error ( 'MCP props are not initialized' ) ;
114+ }
68115
69- initMcpServer ( {
70- server : await this . server ,
71- clientOptions : this . props . clientProps ,
72- mcpOptions : this . props . clientConfig ,
73- } ) ;
116+ configureLogger ( { level : 'info' , pretty : false } ) ;
117+
118+ const server = await buildMcpServer ( this . props . clientConfig ?. stainlessApiKey ) ;
119+
120+ await initMcpServer ( {
121+ server,
122+ clientOptions : this . props . clientProps ,
123+ mcpOptions : this . props . clientConfig ,
124+ } ) ;
125+
126+ this . #resolveServer( server ) ;
127+ } catch ( error ) {
128+ this . #rejectServer( error ) ;
129+ throw error ;
130+ }
74131 }
75132}
76133
0 commit comments