Path: blob/main/extensions/copilot/src/extension/mcp/vscode-node/mcpToolCallingLoopPrompt.tsx
13400 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { BasePromptElementProps, PromptElement, TextChunk, UserMessage } from '@vscode/prompt-tsx';6import { JsonSchema } from '../../../platform/configuration/common/jsonSchema';7import { GenericBasePromptElementProps } from '../../context/node/resolvers/genericPanelIntentInvocation';8import { InstructionMessage } from '../../prompts/node/base/instructionMessage';9import { Tag } from '../../prompts/node/base/tag';10import { HistoryWithInstructions } from '../../prompts/node/panel/conversationHistory';11import { ChatToolCalls } from '../../prompts/node/panel/toolCalling';12import { CopilotToolMode } from '../../tools/common/toolsRegistry';13import { McpPickRef, QuickInputTool, QuickPickTool } from './mcpToolCallingTools';1415export interface IMcpToolCallingLoopPromptContext {16packageName: string;17packageType: 'npm' | 'pip' | 'docker' | 'nuget';18packageReadme: string | undefined;19packageVersion: string | undefined;20targetSchema: JsonSchema;21pickRef: McpPickRef;22}2324export interface IMcpToolCallingLoopProps extends GenericBasePromptElementProps, IMcpToolCallingLoopPromptContext { }2526const packageTypePreferredCommands = {27pip: (name: string, version: string | undefined) => `uvx ${name.replaceAll('-', '_')}` + (version ? `==${version}` : ''),28npm: (name: string, version: string | undefined) => `npx ${name}` + (version ? `@${version}` : ''),29docker: (name: string, _version: string | undefined) => `docker run -i --rm ${name}`,30nuget: (name: string, version: string | undefined) => `dnx ${name}` + (version ? `@${version}` : '') + ` --yes`,31};3233export class McpToolCallingLoopPrompt extends PromptElement<IMcpToolCallingLoopProps> {34async render() {35const { packageType, packageName, packageVersion, pickRef, packageReadme } = this.props;36const { history, toolCallRounds = [], toolCallResults = {} } = this.props.promptContext;3738// We do kind of a 'special' thing here to have the tool only available to *this* prompt because39// we're in a quickpick flow (and don't really want the tool generally available)40for (const round of toolCallRounds) {41for (const tool of round.toolCalls) {42if (toolCallResults[tool.id]) {43// no-op44} else if (tool.name === QuickInputTool.ID) {45toolCallResults[tool.id] = await QuickInputTool.invoke(pickRef, JSON.parse(tool.arguments));46} else if (tool.name === QuickPickTool.ID) {47toolCallResults[tool.id] = await QuickPickTool.invoke(pickRef, JSON.parse(tool.arguments));48}49}50}5152const hasMcpJson = packageReadme?.includes('"mcpServers":');53const command = packageTypePreferredCommands[packageType](packageName, packageVersion);5455return (56<>57<HistoryWithInstructions flexGrow={1} passPriority historyPriority={700} history={history}>58<InstructionMessage>59<Tag name='instructions'>60You are an expert in reading documentation and extracting relevant results.<br />61A developer is setting up a Model Context Protocol (MCP) server based on a {packageType} package. Your task is to create a configuration for the server matching the provided JSON schema.<br />62{hasMcpJson ? <InstructionsWithMcpJson command={command} packageVersion={packageVersion} /> : <InstructionsWithout command={command} packageVersion={packageVersion} />}63<br />64<br />65When using a tool, follow the JSON schema very carefully and make sure to include all required fields. DO NOT write out a JSON codeblock with the tool inputs.<br />66</Tag>67<Tag name='example'>68<Tag name='request'>69User: I want to run the npm package `@modelcontextprotocol/server-redis` as an MCP server. This is its readme:<br /><br />70{redisExampleReadme}71</Tag>72<Tag name='response'>73{hasMcpJson && <>The readme has an example confirmation I'll work off of:<br />${clauseExampleConfiguration}</>}<br />74Based on {hasMcpJson ? 'this example' : 'the documentation'}, I need the following information to run the MCP server:<br />75- Redis hostname<br />76- Redis port number<br />77- Redis password (optional)<br />78<br />79I will now ask for this information.<br />80[[`{QuickInputTool.ID}` called requesting Redis hostname]]: "redis.example.com"<br />81[[`{QuickInputTool.ID}` called requesting Redis port number]]: "3000"<br />82[[`{QuickInputTool.ID}` called requesting Redis port password]]: ""<br />83<br />84{!hasMcpJson && <>Based on this data, the command needed to run the MCP server is `npx @modelcontextprotocol/server-redis redis://example.com:6379`</>}85Based on this data, the command needed to run the MCP server is `npx @modelcontextprotocol/server-redis redis://example.com:6379`<br />86<br />87Here is the JSON object that matches the provided schema:<br />88{redisExampleConfig}89</Tag>90</Tag>91</InstructionMessage>92</HistoryWithInstructions>93<UserMessage flexGrow={3}>94I want to run the {packageType} package `{packageName}` as an MCP server. This is its readme:<br />95<Tag name='readme'>{this.props.packageReadme}</Tag>9697The schema for the final JSON object is:<br />9899<Tag name='schema' flexGrow={1}>100<TextChunk breakOnWhitespace>101{JSON.stringify(this.props.targetSchema, null, 2)}102</TextChunk>103</Tag>104</UserMessage>105<ChatToolCalls priority={899} flexGrow={2} promptContext={this.props.promptContext} toolCallRounds={toolCallRounds} toolCallResults={toolCallResults} toolCallMode={CopilotToolMode.FullContext} />106</>107);108}109}110111class InstructionsWithMcpJson extends PromptElement<{ command: string; packageVersion: string | undefined } & BasePromptElementProps> {112render() {113const [command, ...args] = this.props.command.split(' ');114return <>115Think step by step:<br />1161. Read the documentation for the MCP server and find the section that discusses setting up a configuration with `mcpServers`. If there are multiple such examples, find the one that works best when run as `{`{"command":"${command}", "args": ["${args.join('", "')}", ...], , "env": { ... } }`}. State this configuration in your response.<br />1172. Determine what placeholders are used in that example that the user would need to fill, such as configuration options, credentials, or API keys.<br />1183. Call the tool `{QuickInputTool.ID}` a maximum of 5 times to gather the placeholder information. You may make multiple calls using this tool in parallel, but the maximum number of questions must be 5.<br />1194. Transform that example configuration entry, replacing or adding any additional information the user gave you, into a JSON object matching the provided schema.<br />120{this.props.packageVersion && <>The package version is {this.props.packageVersion}, make sure your command runs the correct version, using the form `{this.props.command}`.<br /></>}1215. Return the resulting JSON object in a markdown code block wrapped with triple backticks (```)<br />122</>;123}124}125class InstructionsWithout extends PromptElement<{ command: string; packageVersion: string | undefined } & BasePromptElementProps> {126render() {127return <>128The MCP server the developer is asking about can be run using the command {this.props.command}, but it may need additional arguments or environment variables to function.<br /><br />129Think step by step:<br />1301. Read the documentation for the MCP server and determine what information you would need to run it on the command line.<br />1312. Call the tool `{QuickInputTool.ID}` a maximum of 5 times to gather the necessary information. You may make multiple calls using this tool in parallel, but the maximum number of questions must be 5.<br />1323. Use that information to construct a set of arguments and environment variables to run the server. <br />133{this.props.packageVersion && <>The package version is {this.props.packageVersion}, make sure your command runs the correct version, using the form `{this.props.command}`.<br /></>}1344. Translate the command, arguments and environment variables into a JSON object that matches the provided schema.<br />1355. Return the resulting JSON object in a markdown code block wrapped with triple backticks (```)<br />136<br />137Follow these rules when constructing your arguments and environment variables:<br />1381. Prefer to use environment variables over arguments when possible, especially for sensitive information. Command-line arguments are not secure.<br />1392. Look carefully in the readme for instructions for how to run the MCP server in `stdio` mode. If there are additional arguments needed to run the MCP server in `stdio` mode, then you MUST include them in your output.<br />1404. Briefly summarize how the above instructions were followed in your response.<br />141</>;142}143}144145const clauseExampleConfiguration = `\`\`\`json146{147"mcpServers": {148"redis": {149"command": "npx",150"args": [151"@modelcontextprotocol/server-redis",152"redis://localhost:6379"153]154}155}156}157\`\`\``;158159const redisExampleReadme = `<readme>160# Redis161162A Model Context Protocol server that provides access to Redis databases. This server enables LLMs to interact with Redis key-value stores through a set of standardized tools.163164## Components165166### Tools167168- **set**169- Set a Redis key-value pair with optional expiration170- Input:171- \`key\` (string): Redis key172- \`value\` (string): Value to store173- \`expireSeconds\` (number, optional): Expiration time in seconds174175- **get**176- Get value by key from Redis177- Input: \`key\` (string): Redis key to retrieve178179- **delete**180- Delete one or more keys from Redis181- Input: \`key\` (string | string[]): Key or array of keys to delete182183- **list**184- List Redis keys matching a pattern185- Input: \`pattern\` (string, optional): Pattern to match keys (default: *)186187## Usage with Claude Desktop188189To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your \`claude_desktop_config.json\`:190191### Docker192193* when running docker on macos, use host.docker.internal if the server is running on the host network (eg localhost)194* Redis URL can be specified as an argument, defaults to "redis://localhost:6379"195196\`\`\`json197{198"mcpServers": {199"redis": {200"command": "docker",201"args": [202"run",203"-i",204"--rm",205"mcp/redis",206"redis://host.docker.internal:6379"]207}208}209}210\`\`\`211212### NPX213214${clauseExampleConfiguration}215</readme>`;216217const redisExampleConfig = `218\`\`\`json219{220"name": "redis",221"command": "npx",222"args": [223"@modelcontextprotocol/server-redis",224"redis://redis.example.com:3000"225]226}227\`\`\`228`;229230231