@@ -20,14 +20,14 @@ import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocatio
20
20
import { McpCommandIds } from './mcpCommandIds.js' ;
21
21
import { IMcpRegistry } from './mcpRegistryTypes.js' ;
22
22
import { McpServer , McpServerMetadataCache } from './mcpServer.js' ;
23
- import { IMcpServer , IMcpService , IMcpTool , McpCollectionDefinition , McpServerCacheState , McpServerDefinition } from './mcpTypes.js' ;
23
+ import { IMcpServer , IMcpService , IMcpTool , McpCollectionDefinition , McpServerCacheState , McpServerDefinition , McpToolName } from './mcpTypes.js' ;
24
24
25
25
interface ISyncedToolData {
26
26
toolData : IToolData ;
27
27
store : DisposableStore ;
28
28
}
29
29
30
- type IMcpServerRec = IReference < IMcpServer > ;
30
+ type IMcpServerRec = IReference < IMcpServer > & { toolPrefix : string } ;
31
31
32
32
export class McpService extends Disposable implements IMcpService {
33
33
@@ -94,6 +94,15 @@ export class McpService extends Disposable implements IMcpService {
94
94
95
95
store . add ( autorun ( reader => {
96
96
const toDelete = new Set ( tools . keys ( ) ) ;
97
+
98
+ // toRegister is deferred until deleting tools that moving a tool between
99
+ // servers (or deleting one instance of a multi-instance server) doesn't cause an error.
100
+ const toRegister : ( ( ) => void ) [ ] = [ ] ;
101
+ const registerTool = ( tool : IMcpTool , toolData : IToolData , store : DisposableStore ) => {
102
+ store . add ( this . _toolsService . registerToolData ( toolData ) ) ;
103
+ store . add ( this . _toolsService . registerToolImplementation ( tool . id , this . _instantiationService . createInstance ( McpToolImplementation , tool , server ) ) ) ;
104
+ } ;
105
+
97
106
for ( const tool of server . tools . read ( reader ) ) {
98
107
const existing = tools . get ( tool . id ) ;
99
108
const collection = this . _mcpRegistry . collections . get ( ) . find ( c => c . id === server . collection . id ) ;
@@ -102,7 +111,7 @@ export class McpService extends Disposable implements IMcpService {
102
111
source : { type : 'mcp' , label : server . definition . label , collectionId : server . collection . id , definitionId : server . definition . id } ,
103
112
icon : Codicon . tools ,
104
113
displayName : tool . definition . annotations ?. title || tool . definition . name ,
105
- toolReferenceName : tool . definition . name ,
114
+ toolReferenceName : tool . id ,
106
115
modelDescription : tool . definition . description ?? '' ,
107
116
userDescription : tool . definition . description ?? '' ,
108
117
inputSchema : tool . definition . inputSchema ,
@@ -112,26 +121,20 @@ export class McpService extends Disposable implements IMcpService {
112
121
tags : [ 'mcp' ] ,
113
122
} ;
114
123
115
- const registerTool = ( store : DisposableStore ) => {
116
- store . add ( this . _toolsService . registerToolData ( toolData ) ) ;
117
- store . add ( this . _toolsService . registerToolImplementation ( tool . id , this . _instantiationService . createInstance ( McpToolImplementation , tool , server ) ) ) ;
118
- } ;
119
-
120
124
if ( existing ) {
121
125
if ( ! equals ( existing . toolData , toolData ) ) {
122
126
existing . toolData = toolData ;
123
127
existing . store . clear ( ) ;
124
128
// We need to re-register both the data and implementation, as the
125
129
// implementation is discarded when the data is removed (#245921)
126
- registerTool ( store ) ;
130
+ registerTool ( tool , toolData , store ) ;
127
131
}
128
132
toDelete . delete ( tool . id ) ;
129
133
} else {
130
134
const store = new DisposableStore ( ) ;
131
- registerTool ( store ) ;
135
+ toRegister . push ( ( ) => registerTool ( tool , toolData , store ) ) ;
132
136
tools . set ( tool . id , { toolData, store } ) ;
133
137
}
134
-
135
138
}
136
139
137
140
for ( const id of toDelete ) {
@@ -141,6 +144,10 @@ export class McpService extends Disposable implements IMcpService {
141
144
tools . delete ( id ) ;
142
145
}
143
146
}
147
+
148
+ for ( const fn of toRegister ) {
149
+ fn ( ) ;
150
+ }
144
151
} ) ) ;
145
152
146
153
store . add ( toDisposable ( ( ) => {
@@ -151,11 +158,12 @@ export class McpService extends Disposable implements IMcpService {
151
158
}
152
159
153
160
public updateCollectedServers ( ) {
161
+ const prefixGenerator = new McpPrefixGenerator ( ) ;
154
162
const definitions = this . _mcpRegistry . collections . get ( ) . flatMap ( collectionDefinition =>
155
- collectionDefinition . serverDefinitions . get ( ) . map ( serverDefinition => ( {
156
- serverDefinition,
157
- collectionDefinition,
158
- } ) )
163
+ collectionDefinition . serverDefinitions . get ( ) . map ( serverDefinition => {
164
+ const toolPrefix = prefixGenerator . generate ( serverDefinition . label ) ;
165
+ return { serverDefinition , collectionDefinition, toolPrefix } ;
166
+ } )
159
167
) ;
160
168
161
169
const nextDefinitions = new Set ( definitions ) ;
@@ -174,7 +182,7 @@ export class McpService extends Disposable implements IMcpService {
174
182
175
183
// Transfer over any servers that are still valid.
176
184
for ( const server of currentServers ) {
177
- const match = definitions . find ( d => defsEqual ( server . object , d ) ) ;
185
+ const match = definitions . find ( d => defsEqual ( server . object , d ) && server . toolPrefix === d . toolPrefix ) ;
178
186
if ( match ) {
179
187
pushMatch ( match , server ) ;
180
188
} else {
@@ -192,12 +200,13 @@ export class McpService extends Disposable implements IMcpService {
192
200
def . serverDefinition . roots ,
193
201
! ! def . collectionDefinition . lazy ,
194
202
def . collectionDefinition . scope === StorageScope . WORKSPACE ? this . workspaceCache : this . userCache ,
203
+ def . toolPrefix ,
195
204
) ;
196
205
197
206
store . add ( object ) ;
198
207
this . _syncTools ( object , store ) ;
199
208
200
- nextServers . push ( { object, dispose : ( ) => store . dispose ( ) } ) ;
209
+ nextServers . push ( { object, dispose : ( ) => store . dispose ( ) , toolPrefix : def . toolPrefix } ) ;
201
210
}
202
211
203
212
transaction ( tx => {
@@ -292,3 +301,18 @@ class McpToolImplementation implements IToolImpl {
292
301
return result ;
293
302
}
294
303
}
304
+
305
+ // Helper class for generating unique MCP tool prefixes
306
+ class McpPrefixGenerator {
307
+ private readonly seenPrefixes = new Set < string > ( ) ;
308
+
309
+ generate ( label : string ) : string {
310
+ const baseToolPrefix = McpToolName . Prefix + label . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 _ . - ] + / g, '_' ) . slice ( 0 , McpToolName . MaxPrefixLen - McpToolName . Prefix . length - 1 ) ;
311
+ let toolPrefix = baseToolPrefix + '_' ;
312
+ for ( let i = 2 ; this . seenPrefixes . has ( toolPrefix ) ; i ++ ) {
313
+ toolPrefix = baseToolPrefix + i + '_' ;
314
+ }
315
+ this . seenPrefixes . add ( toolPrefix ) ;
316
+ return toolPrefix ;
317
+ }
318
+ }
0 commit comments