55 ListToolsRequestSchema ,
66 type Tool ,
77} from "@modelcontextprotocol/sdk/types.js" ;
8- import { jsonSchema , type Schema } from "ai" ;
8+ import type { Schema } from "ai" ;
99import type { McpSettingsSchema } from "./service/tools.ts" ;
1010import {
1111 Server ,
@@ -19,6 +19,10 @@ import type { JSONSchema, ToolCallback } from "./types.ts";
1919import { registerAgenticTool } from "./workflow/agentic-tool-registrar.ts" ;
2020import { registerAgenticWorkflowTool } from "./workflow/workflow-tool-registrar.ts" ;
2121
22+ interface ComposedTool extends Tool {
23+ execute : ToolCallback ;
24+ }
25+
2226const ALL_TOOLS_PLACEHOLDER = "__ALL__" ;
2327const ACTION_KEY = "action" ;
2428
@@ -80,7 +84,7 @@ export class ComposableMCPServer extends Server {
8084 description : string ,
8185 paramsSchema : Schema < T > ,
8286 cb : ( args : T , extra ?: unknown ) => unknown ,
83- internal : boolean = false ,
87+ internal : boolean = false
8488 ) {
8589 if ( ! internal ) {
8690 const newTool : Tool = {
@@ -144,7 +148,8 @@ export class ComposableMCPServer extends Server {
144148 throw new Error ( `Tool ${ name } not found` ) ;
145149 }
146150
147- const callback = this . nameToCb . get ( resolvedName ) ||
151+ const callback =
152+ this . nameToCb . get ( resolvedName ) ||
148153 this . internalTools . get ( resolvedName ) ?. callback ||
149154 this . hiddenTools . get ( resolvedName ) ||
150155 this . composedTools . get ( resolvedName ) ;
@@ -154,8 +159,8 @@ export class ComposableMCPServer extends Server {
154159 }
155160
156161 // Apply args transformation if override exists
157- const override = this . toolOverrides . get ( name ) ||
158- this . toolOverrides . get ( resolvedName ) ;
162+ const override =
163+ this . toolOverrides . get ( name ) || this . toolOverrides . get ( resolvedName ) ;
159164 const processedArgs = override ?. args ? override . args ( args ) : args ;
160165
161166 return await callback ( processedArgs ) ;
@@ -166,7 +171,7 @@ export class ComposableMCPServer extends Server {
166171 */
167172 callInternalTool ( name : string , args : unknown ) : Promise < unknown > {
168173 console . warn (
169- `callInternalTool() is deprecated. Use callTool() instead for: ${ name } ` ,
174+ `callInternalTool() is deprecated. Use callTool() instead for: ${ name } `
170175 ) ;
171176 return this . callTool ( name , args ) ;
172177 }
@@ -194,7 +199,7 @@ export class ComposableMCPServer extends Server {
194199 * Get internal tool schema by name
195200 */
196201 getInternalToolSchema (
197- name : string ,
202+ name : string
198203 ) : { description : string ; schema : JSONSchema } | undefined {
199204 const internalTool = this . internalTools . get ( name ) ;
200205 if ( internalTool ) {
@@ -221,7 +226,7 @@ export class ComposableMCPServer extends Server {
221226 name : string ,
222227 description : string ,
223228 depsConfig : z . infer < typeof McpSettingsSchema > = { mcpServers : { } } ,
224- options : ComposeDefinition [ "options" ] = { mode : "agentic" } ,
229+ options : ComposeDefinition [ "options" ] = { mode : "agentic" }
225230 ) {
226231 const { tagToResults, $ } = parseTags ( description , [ "tool" , "fn" ] ) ;
227232
@@ -239,8 +244,8 @@ export class ComposableMCPServer extends Server {
239244 }
240245 } ) ;
241246
242- // Filter tools and transform scoped tool names to valid action identifierss
243- const tools = await composeMcpDepTools (
247+ // Filter tools and transform scoped tool names to valid action identifiers
248+ const { tools, cleanupClients } = await composeMcpDepTools (
244249 depsConfig ,
245250 ( { mcpName, toolNameWithScope, toolId } ) => {
246251 const matchingStep = options . steps ?. find ( ( step ) =>
@@ -261,7 +266,7 @@ export class ComposableMCPServer extends Server {
261266
262267 description = description . replace (
263268 $ ( tool ) . prop ( "outerHTML" ) ! ,
264- `<action ${ ACTION_KEY } ="${ toolId } "/>` ,
269+ `<action ${ ACTION_KEY } ="${ toolId } "/>`
265270 ) ;
266271 if ( selectAll ) {
267272 return true ;
@@ -271,8 +276,21 @@ export class ComposableMCPServer extends Server {
271276 tool . attribs . name === toolId
272277 ) ;
273278 } ) ;
274- } ,
275- ) ;
279+ }
280+ ) as { tools : Record < string , ComposedTool > ; cleanupClients : ( ) => Promise < void > } ;
281+
282+ // Cleanup clients when server is closed
283+ this . onclose = async ( ) => {
284+ await cleanupClients ( ) ;
285+ console . log ( `[${ name } ] MCP server closed, cleaned up dependent clients.` ) ;
286+ } ;
287+ this . onerror = async ( error ) => {
288+ console . error ( `[${ name } ] MCP server error:` , error ) ;
289+ await cleanupClients ( ) ;
290+ console . log (
291+ `[${ name } ] MCP server error handled, cleaned up dependent clients.`
292+ ) ;
293+ } ;
276294
277295 // Apply tool overrides
278296 Object . entries ( tools ) . forEach ( ( [ toolId , tool ] ) => {
@@ -281,12 +299,10 @@ export class ComposableMCPServer extends Server {
281299
282300 // If no direct override found, check if we have an override for the dot notation equivalent
283301 if ( ! override ) {
284- for (
285- const [
286- overrideName ,
287- overrideOptions ,
288- ] of this . toolOverrides . entries ( )
289- ) {
302+ for ( const [
303+ overrideName ,
304+ overrideOptions ,
305+ ] of this . toolOverrides . entries ( ) ) {
290306 // Build the mapping during processing
291307 const dotNotationId = toolId . replace ( / _ / g, "." ) ;
292308 const underscoreNotationId = overrideName . replace ( / \. / g, "_" ) ;
@@ -333,22 +349,18 @@ export class ComposableMCPServer extends Server {
333349 // For agentic interface: external tools (non-hidden) + internal tools
334350 const allToolNames = [ ...externalToolNames , ...internalToolNames ] ;
335351 console . log (
336- `[${ name } ][composed tools] external: ${
337- externalToolNames . join (
338- ", " ,
339- )
340- } | internal: ${ internalToolNames . join ( ", " ) } `,
352+ `[${ name } ][composed tools] external: ${ externalToolNames . join (
353+ ", "
354+ ) } | internal: ${ internalToolNames . join ( ", " ) } `
341355 ) ;
342356
343357 const depGroups : Record < string , unknown > = { } ;
344358 toolNameToDetailList . forEach ( ( [ toolName , tool ] ) => {
345359 if ( ! tool ) {
346360 throw new Error (
347- `Action ${ toolName } not found, available action list: ${
348- allToolNames . join (
349- ", " ,
350- )
351- } `,
361+ `Action ${ toolName } not found, available action list: ${ allToolNames . join (
362+ ", "
363+ ) } `
352364 ) ;
353365 }
354366
@@ -362,9 +374,10 @@ export class ComposableMCPServer extends Server {
362374 baseSchema . type === "object" && baseSchema . properties
363375 ? baseSchema . properties
364376 : { } ;
365- const baseRequired = baseSchema . type === "object" && baseSchema . required
366- ? baseSchema . required
367- : [ ] ;
377+ const baseRequired =
378+ baseSchema . type === "object" && Array . isArray ( baseSchema . required )
379+ ? baseSchema . required
380+ : [ ] ;
368381
369382 const updatedProperties = updateRefPaths ( baseProperties , toolName ) ;
370383
0 commit comments