mcp: implement McpUiUpdateModelContextRequest handler - #289474
Conversation
Implements the ui/update-model-context MCP Apps protocol method to allow MCP UIs to update the chat widget's model context with structured data and content attachments. Each update replaces only the context added by that specific MCP app instance, preserving user-added attachments. - Maps MCP content blocks to chat attachments: - Images become image attachments - Resource links become file attachments using McpResourceURI - Text blocks become generic attachments with preview and tooltip - Structured content becomes a single JSON attachment formatted nicely - Uses hashed server ID prefix to track and replace only app-specific context - Announces updateModelContext capability in host capabilities Fixes #289473 (Commit message generated by Copilot)
There was a problem hiding this comment.
Pull request overview
This PR implements the ui/update-model-context MCP Apps protocol method to allow MCP UIs to update the chat widget's model context with structured data and content attachments. The implementation uses a hash-based ID prefix to track and replace only the context added by a specific MCP app instance, preserving user-added attachments.
Changes:
- Adds
McpUiUpdateModelContextRequestinterface to the protocol definition with support for content blocks and structured content - Extends
IGenericChatRequestVariableEntryto support tooltips for generic attachments - Implements
_handleUpdateModelContexthandler that converts MCP content blocks to chat attachments - Updates attachment widget to support tooltips for generic attachments
- Announces
updateModelContextcapability in host capabilities with supported content block modalities
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/vs/workbench/contrib/mcp/common/modelContextProtocolApps.ts |
Adds protocol definition for ui/update-model-context request, supported content block modalities interface, and capability announcement in host capabilities |
src/vs/workbench/contrib/chat/common/attachments/chatVariableEntries.ts |
Adds optional tooltip field to IGenericChatRequestVariableEntry for displaying detailed information |
src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatMcpAppModel.ts |
Implements handler that maps MCP content blocks to chat attachments, manages app-specific context with hashed ID prefix, and announces capability |
src/vs/workbench/contrib/chat/browser/attachments/chatAttachmentWidgets.ts |
Extends tooltip hover setup to support generic attachments in addition to string variable entries |
Comments suppressed due to low confidence (3)
src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatMcpAppModel.ts:550
- The idPrefix uses a hashed server definition ID but generates incremental indices (entryIndex++). If updateModelContext is called multiple times with varying numbers of content items, this could lead to gaps in the indices. While this doesn't break functionality since IDs only need to be unique and the prefix-based deletion works correctly, consider using a more deterministic ID scheme that doesn't depend on iteration order, or use the index from iteration directly if the order is guaranteed to be stable.
const idPrefix = `mcpui-context-${hash(this.renderData.serverDefinitionId)}-`;
const toDelete = widget.attachmentModel.getAttachmentIDs();
const idsToDelete = Array.from(toDelete).filter(id => id.startsWith(idPrefix));
const entries: IChatRequestVariableEntry[] = [];
let entryIndex = 0;
if (params.content) {
for (const block of params.content) {
const id = `${idPrefix}${entryIndex++}`;
src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatMcpAppModel.ts:576
- The text content preview truncates to 20 characters with an ellipsis, but empty or very short text blocks might not provide useful previews. Consider adding a minimum length check or a fallback name like "Text Block" when the preview would be too short or consist only of whitespace.
} else if (block.type === 'text') {
const preview = block.text.replaceAll(/\s+/g, ' ').trim();
const truncateTo = 20;
entries.push({
kind: 'generic',
value: block.text,
id,
tooltip: new MarkdownString().appendCodeblock('plaintext', block.text),
name: preview.length > truncateTo ? preview.slice(0, truncateTo) + '…' : preview,
});
src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatMcpAppModel.ts:596
- The new _handleUpdateModelContext method lacks test coverage. Given that the codebase has comprehensive test coverage for MCP protocol handlers (in src/vs/workbench/contrib/mcp/test/common/) and chat features (in src/vs/workbench/contrib/chat/test/), consider adding unit tests for this handler to verify: (1) correct handling of different content block types, (2) proper ID prefix generation and deletion of old context, (3) behavior when widget is not found, and (4) handling of empty or missing content/structuredContent.
private async _handleUpdateModelContext(params: McpApps.McpUiUpdateModelContextRequest['params']): Promise<MCP.EmptyResult> {
const widget = this._chatWidgetService.getWidgetBySessionResource(this.renderData.sessionResource);
if (!widget) {
return {};
}
const idPrefix = `mcpui-context-${hash(this.renderData.serverDefinitionId)}-`;
const toDelete = widget.attachmentModel.getAttachmentIDs();
const idsToDelete = Array.from(toDelete).filter(id => id.startsWith(idPrefix));
const entries: IChatRequestVariableEntry[] = [];
let entryIndex = 0;
if (params.content) {
for (const block of params.content) {
const id = `${idPrefix}${entryIndex++}`;
if (block.type === 'image') {
entries.push({
kind: 'image',
value: decodeBase64(block.data).buffer,
id,
name: 'Image',
mimeType: block.mimeType,
});
} else if (block.type === 'resource_link') {
const uri = McpResourceURI.fromServer({ id: this.renderData.serverDefinitionId, label: '' }, block.uri);
entries.push({
kind: 'file',
value: uri,
id,
name: basename(uri),
});
} else if (block.type === 'text') {
const preview = block.text.replaceAll(/\s+/g, ' ').trim();
const truncateTo = 20;
entries.push({
kind: 'generic',
value: block.text,
id,
tooltip: new MarkdownString().appendCodeblock('plaintext', block.text),
name: preview.length > truncateTo ? preview.slice(0, truncateTo) + '…' : preview,
});
}
}
}
if (params.structuredContent && Object.keys(params.structuredContent).length > 0) {
const id = `${idPrefix}structured`;
const value = JSON.stringify(params.structuredContent, null, 2);
entries.push({
kind: 'generic',
value,
tooltip: new MarkdownString().appendCodeblock('json', value),
id,
name: 'UI Data',
});
}
widget.attachmentModel.updateContext(idsToDelete, entries);
return {};
}
Implements the ui/update-model-context MCP Apps protocol method to allow
MCP UIs to update the chat widget's model context with structured data and
content attachments. Each update replaces only the context added by that
specific MCP app instance, preserving user-added attachments.
Fixes #289473
(Commit message generated by Copilot)