11/**
22 * MCP Server factory for Arcane
3+ *
4+ * Provides two factory functions:
5+ * - createArcaneServer(): Full server for single-connection transports (stdio)
6+ * - createSessionServer(): Lightweight server for HTTP sessions that shares
7+ * tool/resource/prompt registrations from a singleton template to avoid
8+ * re-registering 180+ tools per session (PERF-01)
39 */
410
511import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
@@ -14,7 +20,8 @@ const require = createRequire(import.meta.url);
1420const { version : VERSION } = require ( "../package.json" ) as { version : string } ;
1521
1622/**
17- * Create and configure the Arcane MCP Server
23+ * Create and configure the Arcane MCP Server (full registration).
24+ * Used by stdio transport where there is only one connection.
1825 */
1926export function createArcaneServer ( ) : McpServer {
2027 // Load configuration
@@ -41,4 +48,91 @@ export function createArcaneServer(): McpServer {
4148 return server ;
4249}
4350
44- export default { createArcaneServer } ;
51+ // ---------------------------------------------------------------------------
52+ // Shared-template optimisation for HTTP sessions (PERF-01)
53+ // ---------------------------------------------------------------------------
54+
55+ /**
56+ * Singleton template McpServer. Created once on first call to
57+ * createSessionServer(). All tools, resources, and prompts are registered
58+ * on this instance; per-session servers share the registrations by
59+ * copying internal references rather than re-registering 180+ tools.
60+ */
61+ let _template : McpServer | null = null ;
62+
63+ /**
64+ * Initialise (or return) the singleton template.
65+ */
66+ function getTemplate ( ) : McpServer {
67+ if ( _template ) return _template ;
68+
69+ loadConfig ( ) ;
70+ logger . info ( `Creating shared McpServer template v${ VERSION } ` ) ;
71+
72+ _template = new McpServer ( { name : "arcane" , version : VERSION } ) ;
73+
74+ registerAllTools ( _template ) ;
75+ registerResources ( _template ) ;
76+ registerPrompts ( _template ) ;
77+
78+ logger . info ( "Shared McpServer template ready" ) ;
79+ return _template ;
80+ }
81+
82+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83+ type AnyRecord = Record < string , any > ;
84+
85+ /**
86+ * Create a lightweight McpServer for an HTTP session.
87+ *
88+ * Instead of calling registerAllTools / registerResources / registerPrompts
89+ * (which would create 180+ tool registrations per session), this copies
90+ * the internal registration dictionaries and request-handler map from a
91+ * shared template so that every session shares the same tool definitions
92+ * and handler closures.
93+ *
94+ * The MCP SDK stores registrations in plain objects
95+ * (_registeredTools, _registeredResources, etc.) and request handlers in
96+ * a Map on the underlying Protocol/Server. By assigning these from the
97+ * template before connecting, the new McpServer is fully configured
98+ * without the overhead of individual registerTool() calls.
99+ */
100+ export function createSessionServer ( ) : McpServer {
101+ const template = getTemplate ( ) ;
102+ const tpl = template as unknown as AnyRecord ;
103+
104+ // Create a bare McpServer (no tool registrations)
105+ const session = new McpServer ( { name : "arcane" , version : VERSION } ) ;
106+ const ses = session as unknown as AnyRecord ;
107+
108+ // --- Share registration dictionaries (read-only references) ---
109+ ses . _registeredTools = tpl . _registeredTools ;
110+ ses . _registeredResources = tpl . _registeredResources ;
111+ ses . _registeredResourceTemplates = tpl . _registeredResourceTemplates ;
112+ ses . _registeredPrompts = tpl . _registeredPrompts ;
113+
114+ // --- Copy initialisation flags so McpServer skips re-setup guards ---
115+ ses . _toolHandlersInitialized = true ;
116+ ses . _resourceHandlersInitialized = true ;
117+ ses . _promptHandlersInitialized = true ;
118+ ses . _completionHandlerInitialized = tpl . _completionHandlerInitialized ;
119+
120+ // --- Copy request handlers & capabilities from the template's Server ---
121+ const tplServer = tpl . server as AnyRecord ;
122+ const sesServer = ses . server as AnyRecord ;
123+
124+ // _requestHandlers is a Map<string, handler>. We copy all entries so
125+ // that tools/list, tools/call, resources/list, etc. are already wired.
126+ const tplHandlers : Map < string , unknown > = tplServer . _requestHandlers ;
127+ const sesHandlers : Map < string , unknown > = sesServer . _requestHandlers ;
128+ for ( const [ method , handler ] of tplHandlers ) {
129+ sesHandlers . set ( method , handler ) ;
130+ }
131+
132+ // Capabilities must be set before connect() (SDK throws otherwise)
133+ sesServer . _capabilities = { ...tplServer . _capabilities } ;
134+
135+ return session ;
136+ }
137+
138+ export default { createArcaneServer, createSessionServer } ;
0 commit comments