@@ -19,7 +19,7 @@ import { Conversation, Turn } from '../../prompt/common/conversation';
19
19
import { McpToolCallingLoop } from './mcpToolCallingLoop' ;
20
20
import { McpPickRef } from './mcpToolCallingTools' ;
21
21
22
- type PackageType = 'npm' | 'pip' | 'docker' ;
22
+ type PackageType = 'npm' | 'pip' | 'docker' | 'nuget' ;
23
23
24
24
interface IValidatePackageArgs {
25
25
type : PackageType ;
@@ -52,6 +52,14 @@ interface PyPiPackageResponse {
52
52
} ;
53
53
}
54
54
55
+ interface NuGetServiceIndexResponse {
56
+ resources ?: Array < { "@id" : string ; "@type" : string } > ;
57
+ }
58
+
59
+ interface NuGetSearchResponse {
60
+ data ?: Array < { id : string ; version : string ; description ?: string ; owners ?: Array < string > } > ;
61
+ }
62
+
55
63
interface DockerHubResponse {
56
64
user ?: string ;
57
65
name ?: string ;
@@ -99,7 +107,7 @@ export class McpSetupCommands extends Disposable {
99
107
const done = ( async ( ) => {
100
108
const fakePrompt = `Generate an MCP configuration for ${ packageName } ` ;
101
109
const mcpLoop = this . instantiationService . createInstance ( McpToolCallingLoop , {
102
- toolCallLimit : 5 ,
110
+ toolCallLimit : 100 , // limited via `getAvailableTools` in the loop
103
111
conversation : new Conversation ( generateUuid ( ) , [ new Turn ( undefined , { type : 'user' , message : fakePrompt } ) ] ) ,
104
112
request : {
105
113
attempt : 0 ,
@@ -194,6 +202,60 @@ export class McpSetupCommands extends Disposable {
194
202
const version = data . info ?. version ;
195
203
this . enqueuePendingSetup ( args . targetConfig , args . name , args . type , data . info ?. description , version ) ;
196
204
return { state : 'ok' , publisher : data . info ?. author || data . info ?. author_email || 'unknown' , version } ;
205
+ } else if ( args . type === 'nuget' ) {
206
+ // read the service index to find the search URL
207
+ // https://learn.microsoft.com/en-us/nuget/api/service-index
208
+ const serviceIndexUrl = `https://api.nuget.org/v3/index.json` ;
209
+ const serviceIndexResponse = await fetch ( serviceIndexUrl ) ;
210
+ if ( ! serviceIndexResponse . ok ) {
211
+ return { state : 'error' , error : `Unable to load the NuGet.org registry service index (${ serviceIndexUrl } )` } ;
212
+ }
213
+
214
+ // find the search query URL
215
+ // https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource
216
+ const serviceIndex = await serviceIndexResponse . json ( ) as NuGetServiceIndexResponse ;
217
+ const searchBaseUrl = serviceIndex . resources ?. find ( resource => resource [ '@type' ] === 'SearchQueryService/3.5.0' ) ?. [ '@id' ] ;
218
+ if ( ! searchBaseUrl ) {
219
+ return { state : 'error' , error : `Package search URL not found in the NuGet.org registry service index` } ;
220
+ }
221
+
222
+ // search for the package by ID
223
+ // https://learn.microsoft.com/en-us/nuget/consume-packages/finding-and-choosing-packages#search-syntax
224
+ const searchQueryUrl = `${ searchBaseUrl } ?q=packageid:${ encodeURIComponent ( args . name ) } &prerelease=true&semVerLevel=2.0.0` ;
225
+ const searchResponse = await fetch ( searchQueryUrl ) ;
226
+ if ( ! searchResponse . ok ) {
227
+ return { state : 'error' , error : `Failed to search for ${ args . name } in then NuGet.org registry` } ;
228
+ }
229
+ const data = await searchResponse . json ( ) as NuGetSearchResponse ;
230
+ if ( ! data . data ?. [ 0 ] ) {
231
+ return { state : 'error' , error : `Package ${ args . name } not found on NuGet.org` } ;
232
+ }
233
+
234
+ const id = data . data [ 0 ] . id ?? args . name ;
235
+ let version = data . data [ 0 ] . version ;
236
+ if ( version . indexOf ( '+' ) !== - 1 ) {
237
+ // NuGet versions can have a + sign for build metadata, we strip it for MCP config and API calls
238
+ // e.g. 1.0.0+build123 -> 1.0.0
239
+ version = version . split ( '+' ) [ 0 ] ;
240
+ }
241
+ const publisher = data . data [ 0 ] . owners ? data . data [ 0 ] . owners . join ( ', ' ) : 'unknown' ;
242
+
243
+ // Try to fetch the package readme
244
+ // https://learn.microsoft.com/en-us/nuget/api/readme-template-resource
245
+ const readmeTemplate = serviceIndex . resources ?. find ( resource => resource [ '@type' ] === 'ReadmeUriTemplate/6.13.0' ) ?. [ '@id' ] ;
246
+ let description = data . data [ 0 ] . description || undefined ;
247
+ if ( readmeTemplate ) {
248
+ const readmeUrl = readmeTemplate
249
+ . replace ( '{lower_id}' , encodeURIComponent ( id . toLowerCase ( ) ) )
250
+ . replace ( '{lower_version}' , encodeURIComponent ( version . toLowerCase ( ) ) ) ;
251
+ const readmeResponse = await fetch ( readmeUrl ) ;
252
+ if ( readmeResponse . ok ) {
253
+ description = await readmeResponse . text ( ) ;
254
+ }
255
+ }
256
+
257
+ this . enqueuePendingSetup ( args . targetConfig , id , args . type , description , version ) ;
258
+ return { state : 'ok' , publisher, version } ;
197
259
} else if ( args . type === 'docker' ) {
198
260
// Docker Hub API uses namespace/repository format
199
261
// Handle both formats: 'namespace/repository' or just 'repository' (assumes 'library/' namespace)
0 commit comments