1+ import { createHash } from "crypto" ;
12import { McpClient , type McpToolDefinition , type McpPromptDefinition , type McpResourceDefinition } from "./mcp-client" ;
23import type { McpServerConfig } from "../settings" ;
34
45const MCP_STARTUP_TIMEOUT_MS = process . env . DEEPCODE_MCP_TIMEOUT
56 ? parseInt ( process . env . DEEPCODE_MCP_TIMEOUT , 10 )
67 : 30_000 ;
78const MCP_CALL_TOOL_TIMEOUT_MS = 60_000 ;
9+ const API_TOOL_NAME_PATTERN = / ^ [ a - z A - Z 0 - 9 _ - ] + $ / ;
10+ const API_TOOL_NAME_MAX_LENGTH = 64 ;
811
912type McpToolEntry = {
1013 serverName : string ;
@@ -27,6 +30,32 @@ export type McpServerStatus = {
2730 resources : string [ ] ;
2831} ;
2932
33+ function buildMcpNamespacedName (
34+ serverName : string ,
35+ toolName : string ,
36+ usedNames : ReadonlySet < string > = new Set ( )
37+ ) : string {
38+ const rawName = buildRawMcpNamespacedName ( serverName , toolName ) ;
39+ const sanitizedName = `mcp__${ sanitizeApiToolNamePart ( serverName ) } __${ sanitizeApiToolNamePart ( toolName ) } ` ;
40+ let candidate = fitApiToolName ( sanitizedName , rawName ) ;
41+ if ( ! usedNames . has ( candidate ) ) {
42+ return candidate ;
43+ }
44+
45+ const hash = hashToolName ( rawName ) ;
46+ candidate = fitApiToolNameWithSuffix ( sanitizedName , `_${ hash } ` ) ;
47+ if ( ! usedNames . has ( candidate ) ) {
48+ return candidate ;
49+ }
50+
51+ for ( let index = 2 ; ; index += 1 ) {
52+ candidate = fitApiToolNameWithSuffix ( sanitizedName , `_${ hash } _${ index } ` ) ;
53+ if ( ! usedNames . has ( candidate ) ) {
54+ return candidate ;
55+ }
56+ }
57+ }
58+
3059export class McpManager {
3160 private clients : McpClient [ ] = [ ] ;
3261 private tools : McpToolEntry [ ] = [ ] ;
@@ -151,8 +180,10 @@ export class McpManager {
151180 const serverTools = await client . listTools ( MCP_STARTUP_TIMEOUT_MS ) ;
152181 if ( this . disposed ) return ;
153182 const toolNamespacedNames : string [ ] = [ ] ;
183+ const usedToolNames = new Set ( this . tools . map ( ( tool ) => tool . namespacedName ) ) ;
154184 for ( const tool of serverTools ) {
155- const namespacedName = `mcp__${ name } __${ tool . name } ` ;
185+ const namespacedName = buildMcpNamespacedName ( name , tool . name , usedToolNames ) ;
186+ usedToolNames . add ( namespacedName ) ;
156187 this . tools . push ( {
157188 serverName : name ,
158189 originalName : tool . name ,
@@ -289,7 +320,7 @@ export class McpManager {
289320 type : "function" as const ,
290321 function : {
291322 name : t . namespacedName ,
292- description : t . definition . description ?? ` ${ t . serverName } : ${ t . originalName } ` ,
323+ description : this . buildMcpToolDescription ( t ) ,
293324 parameters : {
294325 type : "object" as const ,
295326 properties : t . definition . inputSchema . properties ,
@@ -413,8 +444,10 @@ export class McpManager {
413444 const serverTools = await client . listTools ( MCP_STARTUP_TIMEOUT_MS ) ;
414445 this . tools = this . tools . filter ( ( t ) => t . serverName !== serverName ) ;
415446 const toolNamespacedNames : string [ ] = [ ] ;
447+ const usedToolNames = new Set ( this . tools . map ( ( tool ) => tool . namespacedName ) ) ;
416448 for ( const tool of serverTools ) {
417- const namespacedName = `mcp__${ serverName } __${ tool . name } ` ;
449+ const namespacedName = buildMcpNamespacedName ( serverName , tool . name , usedToolNames ) ;
450+ usedToolNames . add ( namespacedName ) ;
418451 this . tools . push ( {
419452 serverName,
420453 originalName : tool . name ,
@@ -450,4 +483,42 @@ export class McpManager {
450483 }
451484 this . onStatusChanged ?.( ) ;
452485 }
486+
487+ private buildMcpToolDescription ( tool : McpToolEntry ) : string {
488+ const description = tool . definition . description ?. trim ( ) ;
489+ const source = `${ tool . serverName } : ${ tool . originalName } ` ;
490+ if ( ! description ) {
491+ return source ;
492+ }
493+ if ( tool . namespacedName === buildRawMcpNamespacedName ( tool . serverName , tool . originalName ) ) {
494+ return description ;
495+ }
496+ return `${ description } \nMCP source: ${ source } ` ;
497+ }
498+ }
499+
500+ function buildRawMcpNamespacedName ( serverName : string , toolName : string ) : string {
501+ return `mcp__${ serverName } __${ toolName } ` ;
502+ }
503+
504+ function sanitizeApiToolNamePart ( value : string ) : string {
505+ const sanitized = value . replace ( / [ ^ a - z A - Z 0 - 9 _ - ] / g, "_" ) ;
506+ return sanitized || "unnamed" ;
507+ }
508+
509+ function fitApiToolName ( name : string , rawName : string ) : string {
510+ if ( API_TOOL_NAME_PATTERN . test ( name ) && name . length <= API_TOOL_NAME_MAX_LENGTH ) {
511+ return name ;
512+ }
513+ return fitApiToolNameWithSuffix ( name , `_${ hashToolName ( rawName ) } ` ) ;
514+ }
515+
516+ function fitApiToolNameWithSuffix ( name : string , suffix : string ) : string {
517+ const maxPrefixLength = API_TOOL_NAME_MAX_LENGTH - suffix . length ;
518+ const prefix = name . slice ( 0 , Math . max ( 1 , maxPrefixLength ) ) ;
519+ return `${ prefix } ${ suffix } ` ;
520+ }
521+
522+ function hashToolName ( value : string ) : string {
523+ return createHash ( "sha256" ) . update ( value ) . digest ( "hex" ) . slice ( 0 , 8 ) ;
453524}
0 commit comments