Skip to content

Commit 99e22e8

Browse files
authored
mcp: adopt proposed completions fix (microsoft#250050)
Refs modelcontextprotocol/modelcontextprotocol#598 Also fixes a bug I noticed where we sometimes didn't put templates in the quickpick
1 parent 13755b3 commit 99e22e8

File tree

5 files changed

+43
-14
lines changed

5 files changed

+43
-14
lines changed

src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,19 +119,15 @@ export class McpResourcePickHelper {
119119
const cts = new CancellationTokenSource();
120120

121121
const vars: Record<string, string | string[]> = {};
122-
for (const variable of todo) {
123-
vars[variable.name] = `$${variable.name.toUpperCase()}`;
124-
}
125-
126122
quickInput.totalSteps = todo.length;
127123
quickInput.ignoreFocusOut = true;
128124

129-
const initialCompletions = todo.map(variable => rt.complete(variable.name, '', cts.token));
125+
const initialCompletions = todo.map(variable => rt.complete(variable.name, '', {}, cts.token));
130126

131127
try {
132128
for (let i = 0; i < todo.length; i++) {
133129
const variable = todo[i];
134-
const resolved = await this._promptForTemplateValue(quickInput, variable, initialCompletions[i], rt.template.resolve(vars), rt);
130+
const resolved = await this._promptForTemplateValue(quickInput, variable, initialCompletions[i], vars, rt);
135131
if (resolved === undefined) {
136132
return undefined;
137133
}
@@ -144,11 +140,18 @@ export class McpResourcePickHelper {
144140
}
145141
}
146142

147-
private _promptForTemplateValue(input: IQuickPick<IQuickPickItem>, variable: IUriTemplateVariable, initialCompletions: Promise<string[]>, uriSoFar: string, rt: IMcpResourceTemplate): Promise<string | undefined> {
143+
private _promptForTemplateValue(input: IQuickPick<IQuickPickItem>, variable: IUriTemplateVariable, initialCompletions: Promise<string[]>, variablesSoFar: Record<string, string | string[]>, rt: IMcpResourceTemplate): Promise<string | undefined> {
148144
const store = new DisposableStore();
149145
const completions = new Map<string, Promise<string[]>>([['', initialCompletions]]);
150146

151-
let placeholder = localize('mcp.resource.template.placeholder', "Value for ${0} in {1}", variable.name.toUpperCase(), uriSoFar.replaceAll('%24', '$'));
147+
const variablesWithPlaceholders = { ...variablesSoFar };
148+
for (const variable of rt.template.components.flatMap(c => typeof c === 'object' ? c.variables : [])) {
149+
if (!variablesWithPlaceholders.hasOwnProperty(variable.name)) {
150+
variablesWithPlaceholders[variable.name] = `$${variable.name.toUpperCase()}`;
151+
}
152+
}
153+
154+
let placeholder = localize('mcp.resource.template.placeholder', "Value for ${0} in {1}", variable.name.toUpperCase(), rt.template.resolve(variablesWithPlaceholders).replaceAll('%24', '$'));
152155
if (variable.optional) {
153156
placeholder += ' (' + localize('mcp.resource.template.optional', "Optional") + ')';
154157
}
@@ -174,7 +177,7 @@ export class McpResourcePickHelper {
174177
const inputValue = input.value;
175178
let promise = completions.get(inputValue);
176179
if (!promise) {
177-
promise = rt.complete(variable.name, inputValue, changeCancellation.token);
180+
promise = rt.complete(variable.name, inputValue, variablesSoFar, changeCancellation.token);
178181
completions.set(inputValue, promise);
179182
}
180183

@@ -240,6 +243,7 @@ export class McpResourcePickHelper {
240243
})(),
241244
server.resourceTemplates(cts.token).then(templates => {
242245
writeInto.unshift(...templates);
246+
onChange(servers);
243247
}).catch(() => {
244248
// no templat support, not rare
245249
}),

src/vs/workbench/contrib/mcp/common/mcpServer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CancellationToken, CancellationTokenSource } from '../../../../base/com
88
import * as json from '../../../../base/common/json.js';
99
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
1010
import { LRUCache } from '../../../../base/common/map.js';
11+
import { mapValues } from '../../../../base/common/objects.js';
1112
import { autorun, autorunWithStore, derived, disposableObservableValue, IDerivedReader, IObservable, ITransaction, observableFromEvent, ObservablePromise, observableValue, transaction } from '../../../../base/common/observable.js';
1213
import { basename } from '../../../../base/common/resources.js';
1314
import { URI } from '../../../../base/common/uri.js';
@@ -842,10 +843,11 @@ class McpResourceTemplate implements IMcpResourceTemplate {
842843
return McpResourceURI.fromServer(this._server.definition, serverUri);
843844
}
844845

845-
async complete(templatePart: string, prefix: string, token?: CancellationToken): Promise<string[]> {
846+
async complete(templatePart: string, prefix: string, alreadyResolved: Record<string, string | string[]>, token?: CancellationToken): Promise<string[]> {
846847
const result = await McpServer.callOn(this._server, h => h.complete({
847848
ref: { type: 'ref/resource', uri: this._definition.uriTemplate },
848-
argument: { name: templatePart, value: prefix, }
849+
argument: { name: templatePart, value: prefix },
850+
resolved: mapValues(alreadyResolved, v => Array.isArray(v) ? v.join('/') : v),
849851
}, token), token);
850852
return result.completion.values;
851853
}

src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ export class McpServerRequestHandler extends Disposable {
521521
/**
522522
* Find completions for an argument
523523
*/
524-
complete(params: MCP.CompleteRequest['params'], token?: CancellationToken): Promise<MCP.CompleteResult> {
525-
return this.sendRequest<MCP.CompleteRequest, MCP.CompleteResult>({ method: 'completion/complete', params }, token);
524+
complete(params: MCP.CompleteRequest2['params'], token?: CancellationToken): Promise<MCP.CompleteResult> {
525+
return this.sendRequest<MCP.CompleteRequest2, MCP.CompleteResult>({ method: 'completion/complete', params }, token);
526526
}
527527
}

src/vs/workbench/contrib/mcp/common/mcpTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ export interface IMcpResourceTemplate {
295295
readonly template: UriTemplate;
296296

297297
/** Gets string completions for the given template part. */
298-
complete(templatePart: string, prefix: string, token: CancellationToken): Promise<string[]>;
298+
complete(templatePart: string, prefix: string, alreadyResolved: Record<string, string | string[]>, token: CancellationToken): Promise<string[]>;
299299

300300
/** Gets the resolved URI from template parts. */
301301
resolveURI(vars: Record<string, unknown>): URI;

src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@
55

66
/* eslint-disable local/code-no-unexternalized-strings */
77

8+
//#region proposals
9+
/**
10+
* MCP protocol proposals.
11+
* - Proposals here MUST have an MCP PR linked to them
12+
* - Proposals here are subject to change and SHALL be removed when
13+
* the upstream MCP PR is merged or closed.
14+
*/
15+
export namespace MCP {
16+
17+
// https://github.com/modelcontextprotocol/modelcontextprotocol/pull/598
18+
export interface CompleteRequest2 extends MCP.CompleteRequest {
19+
params: MCP.CompleteRequest['params'] & {
20+
/**
21+
* Previously-resolved variables in a URI template. The keys of the object
22+
* are be the template's variable expressions including surrounding braces.
23+
*/
24+
resolved?: { [key: string]: string };
25+
};
26+
}
27+
}
28+
29+
//#endregion
30+
831
/**
932
* Schema updated from the Model Context Protocol repository at
1033
* https://github.com/modelcontextprotocol/specification/tree/main/schema

0 commit comments

Comments
 (0)