From ec423a715cc4d4ce11ec5eb09491eb5dff41067a Mon Sep 17 00:00:00 2001 From: Diarmid Mackenzie Date: Tue, 25 Mar 2025 17:08:17 +0000 Subject: [PATCH] Fetch instructions (#1869) * Code for new fetch_instructions tool * Call parameter for fetch_instructions task, not text * Additional places that fetch_instructions needs to be added. * Pass necessary objects into create MCP server code * Update snapshots to reflect changes to prompts * Fixes from testing * Move guidance on creating project modes to fetchable instructions * i18n for new prompt Translations suggested by Roo. * Missing translation * Another missing i18n update * Missing Catalan translation * Re-use content parameter on ClineSayTool * Remove space from zh-TW translation This is consistent with other translations Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * PR review - suggested changes to prompts * Slightly more conservative in terms of text pruning from default prompt * Move additional detail about mode creation into fetch_instructions instructions --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- src/core/Cline.ts | 59 ++ src/core/assistant-message/index.ts | 7 + .../__snapshots__/system.test.ts.snap | 983 ++++-------------- .../prompts/instructions/create-mcp-server.ts | 404 +++++++ src/core/prompts/instructions/create-mode.ts | 52 + src/core/prompts/instructions/instructions.ts | 25 + src/core/prompts/sections/mcp-servers.ts | 401 +------ src/core/prompts/sections/modes.ts | 45 +- src/core/prompts/tools/fetch-instructions.ts | 14 + src/core/prompts/tools/index.ts | 3 + src/shared/ExtensionMessage.ts | 1 + src/shared/tool-groups.ts | 3 +- webview-ui/src/components/chat/ChatRow.tsx | 15 + webview-ui/src/i18n/locales/ca/chat.json | 3 + webview-ui/src/i18n/locales/de/chat.json | 3 + webview-ui/src/i18n/locales/en/chat.json | 3 + webview-ui/src/i18n/locales/es/chat.json | 3 + webview-ui/src/i18n/locales/fr/chat.json | 3 + webview-ui/src/i18n/locales/hi/chat.json | 3 + webview-ui/src/i18n/locales/it/chat.json | 3 + webview-ui/src/i18n/locales/ja/chat.json | 3 + webview-ui/src/i18n/locales/ko/chat.json | 3 + webview-ui/src/i18n/locales/pl/chat.json | 3 + webview-ui/src/i18n/locales/pt-BR/chat.json | 3 + webview-ui/src/i18n/locales/tr/chat.json | 3 + webview-ui/src/i18n/locales/vi/chat.json | 3 + webview-ui/src/i18n/locales/zh-CN/chat.json | 3 + webview-ui/src/i18n/locales/zh-TW/chat.json | 3 + 28 files changed, 843 insertions(+), 1214 deletions(-) create mode 100644 src/core/prompts/instructions/create-mcp-server.ts create mode 100644 src/core/prompts/instructions/create-mode.ts create mode 100644 src/core/prompts/instructions/instructions.ts create mode 100644 src/core/prompts/tools/fetch-instructions.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 63bffed83f..7bb4f4628b 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -30,6 +30,7 @@ import { everyLineHasLineNumbers, } from "../integrations/misc/extract-text" import { countFileLines } from "../integrations/misc/line-counter" +import { fetchInstructions } from "./prompts/instructions/instructions" import { ExitCodeDetails } from "../integrations/terminal/TerminalProcess" import { Terminal } from "../integrations/terminal/Terminal" import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry" @@ -1371,6 +1372,8 @@ export class Cline extends EventEmitter { return `[${block.name} for '${block.params.command}']` case "read_file": return `[${block.name} for '${block.params.path}']` + case "fetch_instructions": + return `[${block.name} for '${block.params.task}']` case "write_to_file": return `[${block.name} for '${block.params.path}']` case "apply_diff": @@ -2396,6 +2399,62 @@ export class Cline extends EventEmitter { } } + case "fetch_instructions": { + const task: string | undefined = block.params.task + const sharedMessageProps: ClineSayTool = { + tool: "fetchInstructions", + content: task, + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: undefined, + } satisfies ClineSayTool) + await this.ask("tool", partialMessage, block.partial).catch(() => {}) + break + } else { + if (!task) { + this.consecutiveMistakeCount++ + pushToolResult( + await this.sayAndCreateMissingParamError("fetch_instructions", "task"), + ) + break + } + + this.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: task, + } satisfies ClineSayTool) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + break + } + + // now fetch the content and provide it to the agent. + const provider = this.providerRef.deref() + const mcpHub = provider?.getMcpHub() + if (!mcpHub) { + throw new Error("MCP hub not available") + } + const diffStrategy = this.diffStrategy + const context = provider?.context + const content = await fetchInstructions(task, { mcpHub, diffStrategy, context }) + if (!content) { + pushToolResult(formatResponse.toolError(`Invalid instructions request: ${task}`)) + break + } + pushToolResult(content) + break + } + } catch (error) { + await handleError("fetch instructions", error) + break + } + } + case "list_files": { const relDirPath: string | undefined = block.params.path const recursiveRaw: string | undefined = block.params.recursive diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index 5409c4bbe2..e59622169e 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -25,6 +25,7 @@ export const toolUseNames = [ "attempt_completion", "switch_mode", "new_task", + "fetch_instructions", ] as const // Converts array of tool call names into a union type ("execute_command" | "read_file" | ...) @@ -59,6 +60,7 @@ export const toolParamNames = [ "message", "cwd", "follow_up", + "task", ] as const export type ToolParamName = (typeof toolParamNames)[number] @@ -82,6 +84,11 @@ export interface ReadFileToolUse extends ToolUse { params: Partial, "path" | "start_line" | "end_line">> } +export interface FetchInstructionsToolUse extends ToolUse { + name: "fetch_instructions" + params: Partial, "task">> +} + export interface WriteToFileToolUse extends ToolUse { name: "write_to_file" params: Partial, "path" | "content" | "line_count">> diff --git a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap index de3c920d19..e7f5e5c3ab 100644 --- a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap +++ b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap @@ -80,6 +80,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -466,6 +479,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -941,6 +967,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -1380,6 +1419,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -1766,6 +1818,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -2152,6 +2217,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -2538,6 +2616,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -2973,6 +3064,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -3252,398 +3356,14 @@ The Model Context Protocol (MCP) enables communication between the system and MC When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool. (No MCP servers currently connected) - ## Creating an MCP Server -The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`. +The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`. -When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration). - -Unless the user specifies otherwise, new local MCP servers should be created in: /mock/mcp/path - -### MCP Server Types and Configuration - -MCP servers can be configured in two ways in the MCP settings file: - -1. Local (Stdio) Server Configuration: -\`\`\`json -{ - "mcpServers": { - "local-weather": { - "command": "node", - "args": ["/path/to/weather-server/build/index.js"], - "env": { - "OPENWEATHER_API_KEY": "your-api-key" - } - } - } -} -\`\`\` - -2. Remote (SSE) Server Configuration: -\`\`\`json -{ - "mcpServers": { - "remote-weather": { - "url": "https://api.example.com/mcp", - "headers": { - "Authorization": "Bearer your-api-key" - } - } - } -} -\`\`\` - -Common configuration options for both types: -- \`disabled\`: (optional) Set to true to temporarily disable the server -- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) -- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation - -### Example Local MCP Server - -For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities. - -The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) - -1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory: - -\`\`\`bash -cd /mock/mcp/path -npx @modelcontextprotocol/create-server weather-server -cd weather-server -# Install dependencies -npm install axios -\`\`\` - -This will create a new project with the following structure: - -\`\`\` -weather-server/ - ├── package.json - { - ... - "type": "module", // added by default, uses ES module syntax (import/export) rather than CommonJS (require/module.exports) (Important to know if you create additional scripts in this server repository like a get-refresh-token.js script) - "scripts": { - "build": "tsc && node -e "require('fs').chmodSync('build/index.js', '755')"", - ... - } - ... - } - ├── tsconfig.json - └── src/ - └── weather-server/ - └── index.ts # Main server implementation -\`\`\` - -2. Replace \`src/index.ts\` with the following: - -\`\`\`typescript -#!/usr/bin/env node -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { - CallToolRequestSchema, - ErrorCode, - ListResourcesRequestSchema, - ListResourceTemplatesRequestSchema, - ListToolsRequestSchema, - McpError, - ReadResourceRequestSchema, -} from '@modelcontextprotocol/sdk/types.js'; -import axios from 'axios'; - -const API_KEY = process.env.OPENWEATHER_API_KEY; // provided by MCP config -if (!API_KEY) { - throw new Error('OPENWEATHER_API_KEY environment variable is required'); -} - -interface OpenWeatherResponse { - main: { - temp: number; - humidity: number; - }; - weather: [{ description: string }]; - wind: { speed: number }; - dt_txt?: string; -} - -const isValidForecastArgs = ( - args: any -): args is { city: string; days?: number } => - typeof args === 'object' && - args !== null && - typeof args.city === 'string' && - (args.days === undefined || typeof args.days === 'number'); - -class WeatherServer { - private server: Server; - private axiosInstance; - - constructor() { - this.server = new Server( - { - name: 'example-weather-server', - version: '0.1.0', - }, - { - capabilities: { - resources: {}, - tools: {}, - }, - } - ); - - this.axiosInstance = axios.create({ - baseURL: 'http://api.openweathermap.org/data/2.5', - params: { - appid: API_KEY, - units: 'metric', - }, - }); - - this.setupResourceHandlers(); - this.setupToolHandlers(); - - // Error handling - this.server.onerror = (error) => console.error('[MCP Error]', error); - process.on('SIGINT', async () => { - await this.server.close(); - process.exit(0); - }); - } - - // MCP Resources represent any kind of UTF-8 encoded data that an MCP server wants to make available to clients, such as database records, API responses, log files, and more. Servers define direct resources with a static URI or dynamic resources with a URI template that follows the format \`[protocol]://[host]/[path]\`. - private setupResourceHandlers() { - // For static resources, servers can expose a list of resources: - this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ - resources: [ - // This is a poor example since you could use the resource template to get the same information but this demonstrates how to define a static resource - { - uri: \`weather://San Francisco/current\`, // Unique identifier for San Francisco weather resource - name: \`Current weather in San Francisco\`, // Human-readable name - mimeType: 'application/json', // Optional MIME type - // Optional description - description: - 'Real-time weather data for San Francisco including temperature, conditions, humidity, and wind speed', - }, - ], - })); - - // For dynamic resources, servers can expose resource templates: - this.server.setRequestHandler( - ListResourceTemplatesRequestSchema, - async () => ({ - resourceTemplates: [ - { - uriTemplate: 'weather://{city}/current', // URI template (RFC 6570) - name: 'Current weather for a given city', // Human-readable name - mimeType: 'application/json', // Optional MIME type - description: 'Real-time weather data for a specified city', // Optional description - }, - ], - }) - ); - - // ReadResourceRequestSchema is used for both static resources and dynamic resource templates - this.server.setRequestHandler( - ReadResourceRequestSchema, - async (request) => { - const match = request.params.uri.match( - /^weather://([^/]+)/current$/ - ); - if (!match) { - throw new McpError( - ErrorCode.InvalidRequest, - \`Invalid URI format: \${request.params.uri}\` - ); - } - const city = decodeURIComponent(match[1]); - - try { - const response = await this.axiosInstance.get( - 'weather', // current weather - { - params: { q: city }, - } - ); - - return { - contents: [ - { - uri: request.params.uri, - mimeType: 'application/json', - text: JSON.stringify( - { - temperature: response.data.main.temp, - conditions: response.data.weather[0].description, - humidity: response.data.main.humidity, - wind_speed: response.data.wind.speed, - timestamp: new Date().toISOString(), - }, - null, - 2 - ), - }, - ], - }; - } catch (error) { - if (axios.isAxiosError(error)) { - throw new McpError( - ErrorCode.InternalError, - \`Weather API error: \${ - error.response?.data.message ?? error.message - }\` - ); - } - throw error; - } - } - ); - } - - /* MCP Tools enable servers to expose executable functionality to the system. Through these tools, you can interact with external systems, perform computations, and take actions in the real world. - * - Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems. - * - While resources and tools are similar, you should prefer to create tools over resources when possible as they provide more flexibility. - */ - private setupToolHandlers() { - this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: [ - { - name: 'get_forecast', // Unique identifier - description: 'Get weather forecast for a city', // Human-readable description - inputSchema: { - // JSON Schema for parameters - type: 'object', - properties: { - city: { - type: 'string', - description: 'City name', - }, - days: { - type: 'number', - description: 'Number of days (1-5)', - minimum: 1, - maximum: 5, - }, - }, - required: ['city'], // Array of required property names - }, - }, - ], - })); - - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (request.params.name !== 'get_forecast') { - throw new McpError( - ErrorCode.MethodNotFound, - \`Unknown tool: \${request.params.name}\` - ); - } - - if (!isValidForecastArgs(request.params.arguments)) { - throw new McpError( - ErrorCode.InvalidParams, - 'Invalid forecast arguments' - ); - } - - const city = request.params.arguments.city; - const days = Math.min(request.params.arguments.days || 3, 5); - - try { - const response = await this.axiosInstance.get<{ - list: OpenWeatherResponse[]; - }>('forecast', { - params: { - q: city, - cnt: days * 8, - }, - }); - - return { - content: [ - { - type: 'text', - text: JSON.stringify(response.data.list, null, 2), - }, - ], - }; - } catch (error) { - if (axios.isAxiosError(error)) { - return { - content: [ - { - type: 'text', - text: \`Weather API error: \${ - error.response?.data.message ?? error.message - }\`, - }, - ], - isError: true, - }; - } - throw error; - } - }); - } - - async run() { - const transport = new StdioServerTransport(); - await this.server.connect(transport); - console.error('Weather MCP server running on stdio'); - } -} - -const server = new WeatherServer(); -server.run().catch(console.error); -\`\`\` - -(Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.) - -3. Build and compile the executable JavaScript file - -\`\`\`bash -npm run build -\`\`\` - -4. Whenever you need an environment variable such as an API key to configure the MCP server, walk the user through the process of getting the key. For example, they may need to create an account and go to a developer dashboard to generate the key. Provide step-by-step instructions and URLs to make it easy for the user to retrieve the necessary information. Then use the ask_followup_question tool to ask the user for the key, in this case the OpenWeather API key. - -5. Install the MCP Server by adding the MCP server configuration to the settings file located at '/mock/settings/path'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object. - -IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false and alwaysAllow=[]. - -\`\`\`json -{ - "mcpServers": { - ..., - "weather": { - "command": "node", - "args": ["/path/to/weather-server/build/index.js"], - "env": { - "OPENWEATHER_API_KEY": "user-provided-api-key" - } - }, - } -} -\`\`\` - -(Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify \`~/Library/Application Support/Claude/claude_desktop_config.json\` on macOS for example. It follows the same format of a top level \`mcpServers\` object.) - -6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section. - -7. Now that you have access to these new tools and resources, you may suggest ways the user can command you to invoke them - for example, with this new weather tool now available, you can invite the user to ask "what's the weather in San Francisco?" - -## Editing MCP Servers - -The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: (None running currently), e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file to make changes to the files. - -However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server. - -# MCP Servers Are Not Always Necessary - -The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that..."). - -Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks. +You can obtain detailed instructions on this topic using the fetch_instructions tool, like this: + +create_mcp_server + ==== @@ -3813,6 +3533,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -4248,6 +3981,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -4696,6 +4442,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -5124,6 +4883,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -5672,6 +5444,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -6134,6 +5919,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -6494,6 +6292,19 @@ Note: When both start_line and end_line are provided, this tool efficiently stre This will return a truncated version of the file with information about total line count and method definitions, helping to prevent context size issues with very large files. +## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server + + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. Parameters: @@ -6858,398 +6669,14 @@ The Model Context Protocol (MCP) enables communication between the system and MC When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool. (No MCP servers currently connected) - ## Creating an MCP Server -The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`. +The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`. -When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration). - -Unless the user specifies otherwise, new local MCP servers should be created in: /mock/mcp/path - -### MCP Server Types and Configuration - -MCP servers can be configured in two ways in the MCP settings file: - -1. Local (Stdio) Server Configuration: -\`\`\`json -{ - "mcpServers": { - "local-weather": { - "command": "node", - "args": ["/path/to/weather-server/build/index.js"], - "env": { - "OPENWEATHER_API_KEY": "your-api-key" - } - } - } -} -\`\`\` - -2. Remote (SSE) Server Configuration: -\`\`\`json -{ - "mcpServers": { - "remote-weather": { - "url": "https://api.example.com/mcp", - "headers": { - "Authorization": "Bearer your-api-key" - } - } - } -} -\`\`\` - -Common configuration options for both types: -- \`disabled\`: (optional) Set to true to temporarily disable the server -- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) -- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation - -### Example Local MCP Server - -For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities. - -The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) - -1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory: - -\`\`\`bash -cd /mock/mcp/path -npx @modelcontextprotocol/create-server weather-server -cd weather-server -# Install dependencies -npm install axios -\`\`\` - -This will create a new project with the following structure: - -\`\`\` -weather-server/ - ├── package.json - { - ... - "type": "module", // added by default, uses ES module syntax (import/export) rather than CommonJS (require/module.exports) (Important to know if you create additional scripts in this server repository like a get-refresh-token.js script) - "scripts": { - "build": "tsc && node -e "require('fs').chmodSync('build/index.js', '755')"", - ... - } - ... - } - ├── tsconfig.json - └── src/ - └── weather-server/ - └── index.ts # Main server implementation -\`\`\` - -2. Replace \`src/index.ts\` with the following: - -\`\`\`typescript -#!/usr/bin/env node -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { - CallToolRequestSchema, - ErrorCode, - ListResourcesRequestSchema, - ListResourceTemplatesRequestSchema, - ListToolsRequestSchema, - McpError, - ReadResourceRequestSchema, -} from '@modelcontextprotocol/sdk/types.js'; -import axios from 'axios'; - -const API_KEY = process.env.OPENWEATHER_API_KEY; // provided by MCP config -if (!API_KEY) { - throw new Error('OPENWEATHER_API_KEY environment variable is required'); -} - -interface OpenWeatherResponse { - main: { - temp: number; - humidity: number; - }; - weather: [{ description: string }]; - wind: { speed: number }; - dt_txt?: string; -} - -const isValidForecastArgs = ( - args: any -): args is { city: string; days?: number } => - typeof args === 'object' && - args !== null && - typeof args.city === 'string' && - (args.days === undefined || typeof args.days === 'number'); - -class WeatherServer { - private server: Server; - private axiosInstance; - - constructor() { - this.server = new Server( - { - name: 'example-weather-server', - version: '0.1.0', - }, - { - capabilities: { - resources: {}, - tools: {}, - }, - } - ); - - this.axiosInstance = axios.create({ - baseURL: 'http://api.openweathermap.org/data/2.5', - params: { - appid: API_KEY, - units: 'metric', - }, - }); - - this.setupResourceHandlers(); - this.setupToolHandlers(); - - // Error handling - this.server.onerror = (error) => console.error('[MCP Error]', error); - process.on('SIGINT', async () => { - await this.server.close(); - process.exit(0); - }); - } - - // MCP Resources represent any kind of UTF-8 encoded data that an MCP server wants to make available to clients, such as database records, API responses, log files, and more. Servers define direct resources with a static URI or dynamic resources with a URI template that follows the format \`[protocol]://[host]/[path]\`. - private setupResourceHandlers() { - // For static resources, servers can expose a list of resources: - this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ - resources: [ - // This is a poor example since you could use the resource template to get the same information but this demonstrates how to define a static resource - { - uri: \`weather://San Francisco/current\`, // Unique identifier for San Francisco weather resource - name: \`Current weather in San Francisco\`, // Human-readable name - mimeType: 'application/json', // Optional MIME type - // Optional description - description: - 'Real-time weather data for San Francisco including temperature, conditions, humidity, and wind speed', - }, - ], - })); - - // For dynamic resources, servers can expose resource templates: - this.server.setRequestHandler( - ListResourceTemplatesRequestSchema, - async () => ({ - resourceTemplates: [ - { - uriTemplate: 'weather://{city}/current', // URI template (RFC 6570) - name: 'Current weather for a given city', // Human-readable name - mimeType: 'application/json', // Optional MIME type - description: 'Real-time weather data for a specified city', // Optional description - }, - ], - }) - ); - - // ReadResourceRequestSchema is used for both static resources and dynamic resource templates - this.server.setRequestHandler( - ReadResourceRequestSchema, - async (request) => { - const match = request.params.uri.match( - /^weather://([^/]+)/current$/ - ); - if (!match) { - throw new McpError( - ErrorCode.InvalidRequest, - \`Invalid URI format: \${request.params.uri}\` - ); - } - const city = decodeURIComponent(match[1]); - - try { - const response = await this.axiosInstance.get( - 'weather', // current weather - { - params: { q: city }, - } - ); - - return { - contents: [ - { - uri: request.params.uri, - mimeType: 'application/json', - text: JSON.stringify( - { - temperature: response.data.main.temp, - conditions: response.data.weather[0].description, - humidity: response.data.main.humidity, - wind_speed: response.data.wind.speed, - timestamp: new Date().toISOString(), - }, - null, - 2 - ), - }, - ], - }; - } catch (error) { - if (axios.isAxiosError(error)) { - throw new McpError( - ErrorCode.InternalError, - \`Weather API error: \${ - error.response?.data.message ?? error.message - }\` - ); - } - throw error; - } - } - ); - } - - /* MCP Tools enable servers to expose executable functionality to the system. Through these tools, you can interact with external systems, perform computations, and take actions in the real world. - * - Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems. - * - While resources and tools are similar, you should prefer to create tools over resources when possible as they provide more flexibility. - */ - private setupToolHandlers() { - this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: [ - { - name: 'get_forecast', // Unique identifier - description: 'Get weather forecast for a city', // Human-readable description - inputSchema: { - // JSON Schema for parameters - type: 'object', - properties: { - city: { - type: 'string', - description: 'City name', - }, - days: { - type: 'number', - description: 'Number of days (1-5)', - minimum: 1, - maximum: 5, - }, - }, - required: ['city'], // Array of required property names - }, - }, - ], - })); - - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (request.params.name !== 'get_forecast') { - throw new McpError( - ErrorCode.MethodNotFound, - \`Unknown tool: \${request.params.name}\` - ); - } - - if (!isValidForecastArgs(request.params.arguments)) { - throw new McpError( - ErrorCode.InvalidParams, - 'Invalid forecast arguments' - ); - } - - const city = request.params.arguments.city; - const days = Math.min(request.params.arguments.days || 3, 5); - - try { - const response = await this.axiosInstance.get<{ - list: OpenWeatherResponse[]; - }>('forecast', { - params: { - q: city, - cnt: days * 8, - }, - }); - - return { - content: [ - { - type: 'text', - text: JSON.stringify(response.data.list, null, 2), - }, - ], - }; - } catch (error) { - if (axios.isAxiosError(error)) { - return { - content: [ - { - type: 'text', - text: \`Weather API error: \${ - error.response?.data.message ?? error.message - }\`, - }, - ], - isError: true, - }; - } - throw error; - } - }); - } - - async run() { - const transport = new StdioServerTransport(); - await this.server.connect(transport); - console.error('Weather MCP server running on stdio'); - } -} - -const server = new WeatherServer(); -server.run().catch(console.error); -\`\`\` - -(Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.) - -3. Build and compile the executable JavaScript file - -\`\`\`bash -npm run build -\`\`\` - -4. Whenever you need an environment variable such as an API key to configure the MCP server, walk the user through the process of getting the key. For example, they may need to create an account and go to a developer dashboard to generate the key. Provide step-by-step instructions and URLs to make it easy for the user to retrieve the necessary information. Then use the ask_followup_question tool to ask the user for the key, in this case the OpenWeather API key. - -5. Install the MCP Server by adding the MCP server configuration to the settings file located at '/mock/settings/path'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object. - -IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false and alwaysAllow=[]. - -\`\`\`json -{ - "mcpServers": { - ..., - "weather": { - "command": "node", - "args": ["/path/to/weather-server/build/index.js"], - "env": { - "OPENWEATHER_API_KEY": "user-provided-api-key" - } - }, - } -} -\`\`\` - -(Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify \`~/Library/Application Support/Claude/claude_desktop_config.json\` on macOS for example. It follows the same format of a top level \`mcpServers\` object.) - -6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section. - -7. Now that you have access to these new tools and resources, you may suggest ways the user can command you to invoke them - for example, with this new weather tool now available, you can invite the user to ask "what's the weather in San Francisco?" - -## Editing MCP Servers - -The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: (None running currently), e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file to make changes to the files. - -However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server. - -# MCP Servers Are Not Always Necessary - -The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that..."). - -Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks. +You can obtain detailed instructions on this topic using the fetch_instructions tool, like this: + +create_mcp_server + ==== diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts new file mode 100644 index 0000000000..917a94f47a --- /dev/null +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -0,0 +1,404 @@ +import { McpHub } from "../../../services/mcp/McpHub" +import { DiffStrategy } from "../../diff/DiffStrategy" + +export async function createMCPServerInstructions( + mcpHub: McpHub | undefined, + diffStrategy: DiffStrategy | undefined, +): Promise { + if (!diffStrategy || !mcpHub) throw new Error("Missing MCP Hub or Diff Strategy") + + return `You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`. + +When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration). + +Unless the user specifies otherwise, new local MCP servers should be created in: ${await mcpHub.getMcpServersPath()} + +### MCP Server Types and Configuration + +MCP servers can be configured in two ways in the MCP settings file: + +1. Local (Stdio) Server Configuration: +\`\`\`json +{ + "mcpServers": { + "local-weather": { + "command": "node", + "args": ["/path/to/weather-server/build/index.js"], + "env": { + "OPENWEATHER_API_KEY": "your-api-key" + } + } + } +} +\`\`\` + +2. Remote (SSE) Server Configuration: +\`\`\`json +{ + "mcpServers": { + "remote-weather": { + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer your-api-key" + } + } + } +} +\`\`\` + +Common configuration options for both types: +- \`disabled\`: (optional) Set to true to temporarily disable the server +- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) +- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation + +### Example Local MCP Server + +For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities. + +The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) + +1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory: + +\`\`\`bash +cd ${await mcpHub.getMcpServersPath()} +npx @modelcontextprotocol/create-server weather-server +cd weather-server +# Install dependencies +npm install axios +\`\`\` + +This will create a new project with the following structure: + +\`\`\` +weather-server/ + ├── package.json + { + ... + "type": "module", // added by default, uses ES module syntax (import/export) rather than CommonJS (require/module.exports) (Important to know if you create additional scripts in this server repository like a get-refresh-token.js script) + "scripts": { + "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", + ... + } + ... + } + ├── tsconfig.json + └── src/ + └── weather-server/ + └── index.ts # Main server implementation +\`\`\` + +2. Replace \`src/index.ts\` with the following: + +\`\`\`typescript +#!/usr/bin/env node +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ErrorCode, + ListResourcesRequestSchema, + ListResourceTemplatesRequestSchema, + ListToolsRequestSchema, + McpError, + ReadResourceRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import axios from 'axios'; + +const API_KEY = process.env.OPENWEATHER_API_KEY; // provided by MCP config +if (!API_KEY) { + throw new Error('OPENWEATHER_API_KEY environment variable is required'); +} + +interface OpenWeatherResponse { + main: { + temp: number; + humidity: number; + }; + weather: [{ description: string }]; + wind: { speed: number }; + dt_txt?: string; +} + +const isValidForecastArgs = ( + args: any +): args is { city: string; days?: number } => + typeof args === 'object' && + args !== null && + typeof args.city === 'string' && + (args.days === undefined || typeof args.days === 'number'); + +class WeatherServer { + private server: Server; + private axiosInstance; + + constructor() { + this.server = new Server( + { + name: 'example-weather-server', + version: '0.1.0', + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + } + ); + + this.axiosInstance = axios.create({ + baseURL: 'http://api.openweathermap.org/data/2.5', + params: { + appid: API_KEY, + units: 'metric', + }, + }); + + this.setupResourceHandlers(); + this.setupToolHandlers(); + + // Error handling + this.server.onerror = (error) => console.error('[MCP Error]', error); + process.on('SIGINT', async () => { + await this.server.close(); + process.exit(0); + }); + } + + // MCP Resources represent any kind of UTF-8 encoded data that an MCP server wants to make available to clients, such as database records, API responses, log files, and more. Servers define direct resources with a static URI or dynamic resources with a URI template that follows the format \`[protocol]://[host]/[path]\`. + private setupResourceHandlers() { + // For static resources, servers can expose a list of resources: + this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ + resources: [ + // This is a poor example since you could use the resource template to get the same information but this demonstrates how to define a static resource + { + uri: \`weather://San Francisco/current\`, // Unique identifier for San Francisco weather resource + name: \`Current weather in San Francisco\`, // Human-readable name + mimeType: 'application/json', // Optional MIME type + // Optional description + description: + 'Real-time weather data for San Francisco including temperature, conditions, humidity, and wind speed', + }, + ], + })); + + // For dynamic resources, servers can expose resource templates: + this.server.setRequestHandler( + ListResourceTemplatesRequestSchema, + async () => ({ + resourceTemplates: [ + { + uriTemplate: 'weather://{city}/current', // URI template (RFC 6570) + name: 'Current weather for a given city', // Human-readable name + mimeType: 'application/json', // Optional MIME type + description: 'Real-time weather data for a specified city', // Optional description + }, + ], + }) + ); + + // ReadResourceRequestSchema is used for both static resources and dynamic resource templates + this.server.setRequestHandler( + ReadResourceRequestSchema, + async (request) => { + const match = request.params.uri.match( + /^weather:\/\/([^/]+)\/current$/ + ); + if (!match) { + throw new McpError( + ErrorCode.InvalidRequest, + \`Invalid URI format: \${request.params.uri}\` + ); + } + const city = decodeURIComponent(match[1]); + + try { + const response = await this.axiosInstance.get( + 'weather', // current weather + { + params: { q: city }, + } + ); + + return { + contents: [ + { + uri: request.params.uri, + mimeType: 'application/json', + text: JSON.stringify( + { + temperature: response.data.main.temp, + conditions: response.data.weather[0].description, + humidity: response.data.main.humidity, + wind_speed: response.data.wind.speed, + timestamp: new Date().toISOString(), + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + if (axios.isAxiosError(error)) { + throw new McpError( + ErrorCode.InternalError, + \`Weather API error: \${ + error.response?.data.message ?? error.message + }\` + ); + } + throw error; + } + } + ); + } + + /* MCP Tools enable servers to expose executable functionality to the system. Through these tools, you can interact with external systems, perform computations, and take actions in the real world. + * - Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems. + * - While resources and tools are similar, you should prefer to create tools over resources when possible as they provide more flexibility. + */ + private setupToolHandlers() { + this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'get_forecast', // Unique identifier + description: 'Get weather forecast for a city', // Human-readable description + inputSchema: { + // JSON Schema for parameters + type: 'object', + properties: { + city: { + type: 'string', + description: 'City name', + }, + days: { + type: 'number', + description: 'Number of days (1-5)', + minimum: 1, + maximum: 5, + }, + }, + required: ['city'], // Array of required property names + }, + }, + ], + })); + + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name !== 'get_forecast') { + throw new McpError( + ErrorCode.MethodNotFound, + \`Unknown tool: \${request.params.name}\` + ); + } + + if (!isValidForecastArgs(request.params.arguments)) { + throw new McpError( + ErrorCode.InvalidParams, + 'Invalid forecast arguments' + ); + } + + const city = request.params.arguments.city; + const days = Math.min(request.params.arguments.days || 3, 5); + + try { + const response = await this.axiosInstance.get<{ + list: OpenWeatherResponse[]; + }>('forecast', { + params: { + q: city, + cnt: days * 8, + }, + }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response.data.list, null, 2), + }, + ], + }; + } catch (error) { + if (axios.isAxiosError(error)) { + return { + content: [ + { + type: 'text', + text: \`Weather API error: \${ + error.response?.data.message ?? error.message + }\`, + }, + ], + isError: true, + }; + } + throw error; + } + }); + } + + async run() { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error('Weather MCP server running on stdio'); + } +} + +const server = new WeatherServer(); +server.run().catch(console.error); +\`\`\` + +(Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.) + +3. Build and compile the executable JavaScript file + +\`\`\`bash +npm run build +\`\`\` + +4. Whenever you need an environment variable such as an API key to configure the MCP server, walk the user through the process of getting the key. For example, they may need to create an account and go to a developer dashboard to generate the key. Provide step-by-step instructions and URLs to make it easy for the user to retrieve the necessary information. Then use the ask_followup_question tool to ask the user for the key, in this case the OpenWeather API key. + +5. Install the MCP Server by adding the MCP server configuration to the settings file located at '${await mcpHub.getMcpSettingsFilePath()}'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object. + +IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false and alwaysAllow=[]. + +\`\`\`json +{ + "mcpServers": { + ..., + "weather": { + "command": "node", + "args": ["/path/to/weather-server/build/index.js"], + "env": { + "OPENWEATHER_API_KEY": "user-provided-api-key" + } + }, + } +} +\`\`\` + +(Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify \`~/Library/Application\ Support/Claude/claude_desktop_config.json\` on macOS for example. It follows the same format of a top level \`mcpServers\` object.) + +6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section. + +7. Now that you have access to these new tools and resources, you may suggest ways the user can command you to invoke them - for example, with this new weather tool now available, you can invite the user to ask "what's the weather in San Francisco?" + +## Editing MCP Servers + +The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: ${ + mcpHub + .getServers() + .map((server) => server.name) + .join(", ") || "(None running currently)" + }, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file${diffStrategy ? " or apply_diff" : ""} to make changes to the files. + +However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server. + +# MCP Servers Are Not Always Necessary + +The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that..."). + +Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks.` +} diff --git a/src/core/prompts/instructions/create-mode.ts b/src/core/prompts/instructions/create-mode.ts new file mode 100644 index 0000000000..fd88dbfb59 --- /dev/null +++ b/src/core/prompts/instructions/create-mode.ts @@ -0,0 +1,52 @@ +import * as path from "path" +import * as vscode from "vscode" +import { promises as fs } from "fs" +import { GlobalFileNames } from "../../../shared/globalFileNames" + +export async function createModeInstructions(context: vscode.ExtensionContext | undefined): Promise { + if (!context) throw new Error("Missing VSCode Extension Context") + + const settingsDir = path.join(context.globalStorageUri.fsPath, "settings") + const customModesPath = path.join(settingsDir, GlobalFileNames.customModes) + + return ` +Custom modes can be configured in two ways: + 1. Globally via '${customModesPath}' (created automatically on startup) + 2. Per-workspace via '.roomodes' in the workspace root directory + +When modes with the same slug exist in both files, the workspace-specific .roomodes version takes precedence. This allows projects to override global modes or define project-specific modes. + + +If asked to create a project mode, create it in .roomodes in the workspace root. If asked to create a global mode, use the global custom modes file. + +- The following fields are required and must not be empty: + * slug: A valid slug (lowercase letters, numbers, and hyphens). Must be unique, and shorter is better. + * name: The display name for the mode + * roleDefinition: A detailed description of the mode's role and capabilities + * groups: Array of allowed tool groups (can be empty). Each group can be specified either as a string (e.g., "edit" to allow editing any file) or with file restrictions (e.g., ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }] to only allow editing markdown files) + +- The customInstructions field is optional. + +- For multi-line text, include newline characters in the string like "This is the first line.\\nThis is the next line.\\n\\nThis is a double line break." + +Both files should follow this structure: +{ + "customModes": [ + { + "slug": "designer", // Required: unique slug with lowercase letters, numbers, and hyphens + "name": "Designer", // Required: mode display name + "roleDefinition": "You are Roo, a UI/UX expert specializing in design systems and frontend development. Your expertise includes:\\n- Creating and maintaining design systems\\n- Implementing responsive and accessible web interfaces\\n- Working with CSS, HTML, and modern frontend frameworks\\n- Ensuring consistent user experiences across platforms", // Required: non-empty + "groups": [ // Required: array of tool groups (can be empty) + "read", // Read files group (read_file, fetch_instructions, search_files, list_files, list_code_definition_names) + "edit", // Edit files group (apply_diff, write_to_file) - allows editing any file + // Or with file restrictions: + // ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], // Edit group that only allows editing markdown files + "browser", // Browser group (browser_action) + "command", // Command group (execute_command) + "mcp" // MCP group (use_mcp_tool, access_mcp_resource) + ], + "customInstructions": "Additional instructions for the Designer mode" // Optional + } + ] +}` +} diff --git a/src/core/prompts/instructions/instructions.ts b/src/core/prompts/instructions/instructions.ts new file mode 100644 index 0000000000..3abfaac0b9 --- /dev/null +++ b/src/core/prompts/instructions/instructions.ts @@ -0,0 +1,25 @@ +import { createMCPServerInstructions } from "./create-mcp-server" +import { createModeInstructions } from "./create-mode" +import { McpHub } from "../../../services/mcp/McpHub" +import { DiffStrategy } from "../../diff/DiffStrategy" +import * as vscode from "vscode" + +interface InstructionsDetail { + mcpHub?: McpHub + diffStrategy?: DiffStrategy + context?: vscode.ExtensionContext +} + +export async function fetchInstructions(text: string, detail: InstructionsDetail): Promise { + switch (text) { + case "create_mcp_server": { + return await createMCPServerInstructions(detail.mcpHub, detail.diffStrategy) + } + case "create_mode": { + return await createModeInstructions(detail.context) + } + default: { + return "" + } + } +} diff --git a/src/core/prompts/sections/mcp-servers.ts b/src/core/prompts/sections/mcp-servers.ts index 530bb374f7..5603b0c385 100644 --- a/src/core/prompts/sections/mcp-servers.ts +++ b/src/core/prompts/sections/mcp-servers.ts @@ -20,7 +20,7 @@ export async function getMcpServersSection( ?.map((tool) => { const schemaStr = tool.inputSchema ? ` Input Schema: - ${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}` + ${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}` : "" return `- ${tool.name}: ${tool.description}\n${schemaStr}` @@ -67,402 +67,13 @@ ${connectedServers}` return ( baseSection + ` - ## Creating an MCP Server -The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`. +The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`use_mcp_tool\` and \`access_mcp_resource\`. -When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration). - -Unless the user specifies otherwise, new local MCP servers should be created in: ${await mcpHub.getMcpServersPath()} - -### MCP Server Types and Configuration - -MCP servers can be configured in two ways in the MCP settings file: - -1. Local (Stdio) Server Configuration: -\`\`\`json -{ - "mcpServers": { - "local-weather": { - "command": "node", - "args": ["/path/to/weather-server/build/index.js"], - "env": { - "OPENWEATHER_API_KEY": "your-api-key" - } - } - } -} -\`\`\` - -2. Remote (SSE) Server Configuration: -\`\`\`json -{ - "mcpServers": { - "remote-weather": { - "url": "https://api.example.com/mcp", - "headers": { - "Authorization": "Bearer your-api-key" - } - } - } -} -\`\`\` - -Common configuration options for both types: -- \`disabled\`: (optional) Set to true to temporarily disable the server -- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) -- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation - -### Example Local MCP Server - -For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities. - -The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) - -1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory: - -\`\`\`bash -cd ${await mcpHub.getMcpServersPath()} -npx @modelcontextprotocol/create-server weather-server -cd weather-server -# Install dependencies -npm install axios -\`\`\` - -This will create a new project with the following structure: - -\`\`\` -weather-server/ - ├── package.json - { - ... - "type": "module", // added by default, uses ES module syntax (import/export) rather than CommonJS (require/module.exports) (Important to know if you create additional scripts in this server repository like a get-refresh-token.js script) - "scripts": { - "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", - ... - } - ... - } - ├── tsconfig.json - └── src/ - └── weather-server/ - └── index.ts # Main server implementation -\`\`\` - -2. Replace \`src/index.ts\` with the following: - -\`\`\`typescript -#!/usr/bin/env node -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { - CallToolRequestSchema, - ErrorCode, - ListResourcesRequestSchema, - ListResourceTemplatesRequestSchema, - ListToolsRequestSchema, - McpError, - ReadResourceRequestSchema, -} from '@modelcontextprotocol/sdk/types.js'; -import axios from 'axios'; - -const API_KEY = process.env.OPENWEATHER_API_KEY; // provided by MCP config -if (!API_KEY) { - throw new Error('OPENWEATHER_API_KEY environment variable is required'); -} - -interface OpenWeatherResponse { - main: { - temp: number; - humidity: number; - }; - weather: [{ description: string }]; - wind: { speed: number }; - dt_txt?: string; -} - -const isValidForecastArgs = ( - args: any -): args is { city: string; days?: number } => - typeof args === 'object' && - args !== null && - typeof args.city === 'string' && - (args.days === undefined || typeof args.days === 'number'); - -class WeatherServer { - private server: Server; - private axiosInstance; - - constructor() { - this.server = new Server( - { - name: 'example-weather-server', - version: '0.1.0', - }, - { - capabilities: { - resources: {}, - tools: {}, - }, - } - ); - - this.axiosInstance = axios.create({ - baseURL: 'http://api.openweathermap.org/data/2.5', - params: { - appid: API_KEY, - units: 'metric', - }, - }); - - this.setupResourceHandlers(); - this.setupToolHandlers(); - - // Error handling - this.server.onerror = (error) => console.error('[MCP Error]', error); - process.on('SIGINT', async () => { - await this.server.close(); - process.exit(0); - }); - } - - // MCP Resources represent any kind of UTF-8 encoded data that an MCP server wants to make available to clients, such as database records, API responses, log files, and more. Servers define direct resources with a static URI or dynamic resources with a URI template that follows the format \`[protocol]://[host]/[path]\`. - private setupResourceHandlers() { - // For static resources, servers can expose a list of resources: - this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ - resources: [ - // This is a poor example since you could use the resource template to get the same information but this demonstrates how to define a static resource - { - uri: \`weather://San Francisco/current\`, // Unique identifier for San Francisco weather resource - name: \`Current weather in San Francisco\`, // Human-readable name - mimeType: 'application/json', // Optional MIME type - // Optional description - description: - 'Real-time weather data for San Francisco including temperature, conditions, humidity, and wind speed', - }, - ], - })); - - // For dynamic resources, servers can expose resource templates: - this.server.setRequestHandler( - ListResourceTemplatesRequestSchema, - async () => ({ - resourceTemplates: [ - { - uriTemplate: 'weather://{city}/current', // URI template (RFC 6570) - name: 'Current weather for a given city', // Human-readable name - mimeType: 'application/json', // Optional MIME type - description: 'Real-time weather data for a specified city', // Optional description - }, - ], - }) - ); - - // ReadResourceRequestSchema is used for both static resources and dynamic resource templates - this.server.setRequestHandler( - ReadResourceRequestSchema, - async (request) => { - const match = request.params.uri.match( - /^weather:\/\/([^/]+)\/current$/ - ); - if (!match) { - throw new McpError( - ErrorCode.InvalidRequest, - \`Invalid URI format: \${request.params.uri}\` - ); - } - const city = decodeURIComponent(match[1]); - - try { - const response = await this.axiosInstance.get( - 'weather', // current weather - { - params: { q: city }, - } - ); - - return { - contents: [ - { - uri: request.params.uri, - mimeType: 'application/json', - text: JSON.stringify( - { - temperature: response.data.main.temp, - conditions: response.data.weather[0].description, - humidity: response.data.main.humidity, - wind_speed: response.data.wind.speed, - timestamp: new Date().toISOString(), - }, - null, - 2 - ), - }, - ], - }; - } catch (error) { - if (axios.isAxiosError(error)) { - throw new McpError( - ErrorCode.InternalError, - \`Weather API error: \${ - error.response?.data.message ?? error.message - }\` - ); - } - throw error; - } - } - ); - } - - /* MCP Tools enable servers to expose executable functionality to the system. Through these tools, you can interact with external systems, perform computations, and take actions in the real world. - * - Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems. - * - While resources and tools are similar, you should prefer to create tools over resources when possible as they provide more flexibility. - */ - private setupToolHandlers() { - this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: [ - { - name: 'get_forecast', // Unique identifier - description: 'Get weather forecast for a city', // Human-readable description - inputSchema: { - // JSON Schema for parameters - type: 'object', - properties: { - city: { - type: 'string', - description: 'City name', - }, - days: { - type: 'number', - description: 'Number of days (1-5)', - minimum: 1, - maximum: 5, - }, - }, - required: ['city'], // Array of required property names - }, - }, - ], - })); - - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (request.params.name !== 'get_forecast') { - throw new McpError( - ErrorCode.MethodNotFound, - \`Unknown tool: \${request.params.name}\` - ); - } - - if (!isValidForecastArgs(request.params.arguments)) { - throw new McpError( - ErrorCode.InvalidParams, - 'Invalid forecast arguments' - ); - } - - const city = request.params.arguments.city; - const days = Math.min(request.params.arguments.days || 3, 5); - - try { - const response = await this.axiosInstance.get<{ - list: OpenWeatherResponse[]; - }>('forecast', { - params: { - q: city, - cnt: days * 8, - }, - }); - - return { - content: [ - { - type: 'text', - text: JSON.stringify(response.data.list, null, 2), - }, - ], - }; - } catch (error) { - if (axios.isAxiosError(error)) { - return { - content: [ - { - type: 'text', - text: \`Weather API error: \${ - error.response?.data.message ?? error.message - }\`, - }, - ], - isError: true, - }; - } - throw error; - } - }); - } - - async run() { - const transport = new StdioServerTransport(); - await this.server.connect(transport); - console.error('Weather MCP server running on stdio'); - } -} - -const server = new WeatherServer(); -server.run().catch(console.error); -\`\`\` - -(Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.) - -3. Build and compile the executable JavaScript file - -\`\`\`bash -npm run build -\`\`\` - -4. Whenever you need an environment variable such as an API key to configure the MCP server, walk the user through the process of getting the key. For example, they may need to create an account and go to a developer dashboard to generate the key. Provide step-by-step instructions and URLs to make it easy for the user to retrieve the necessary information. Then use the ask_followup_question tool to ask the user for the key, in this case the OpenWeather API key. - -5. Install the MCP Server by adding the MCP server configuration to the settings file located at '${await mcpHub.getMcpSettingsFilePath()}'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object. - -IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false and alwaysAllow=[]. - -\`\`\`json -{ - "mcpServers": { - ..., - "weather": { - "command": "node", - "args": ["/path/to/weather-server/build/index.js"], - "env": { - "OPENWEATHER_API_KEY": "user-provided-api-key" - } - }, - } -} -\`\`\` - -(Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify \`~/Library/Application\ Support/Claude/claude_desktop_config.json\` on macOS for example. It follows the same format of a top level \`mcpServers\` object.) - -6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section. - -7. Now that you have access to these new tools and resources, you may suggest ways the user can command you to invoke them - for example, with this new weather tool now available, you can invite the user to ask "what's the weather in San Francisco?" - -## Editing MCP Servers - -The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: ${ - mcpHub - .getServers() - .map((server) => server.name) - .join(", ") || "(None running currently)" - }, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file${diffStrategy ? " or apply_diff" : ""} to make changes to the files. - -However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server. - -# MCP Servers Are Not Always Necessary - -The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that..."). - -Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks.` +You can obtain detailed instructions on this topic using the fetch_instructions tool, like this: + +create_mcp_server +` ) } diff --git a/src/core/prompts/sections/modes.ts b/src/core/prompts/sections/modes.ts index 0015b8917c..4f79d9dc9d 100644 --- a/src/core/prompts/sections/modes.ts +++ b/src/core/prompts/sections/modes.ts @@ -7,7 +7,6 @@ import { GlobalFileNames } from "../../../shared/globalFileNames" export async function getModesSection(context: vscode.ExtensionContext): Promise { const settingsDir = path.join(context.globalStorageUri.fsPath, "settings") await fs.mkdir(settingsDir, { recursive: true }) - const customModesPath = path.join(settingsDir, GlobalFileNames.customModes) // Get all modes with their overrides from extension state const allModes = await getAllModesWithPrompts(context) @@ -25,45 +24,11 @@ ${allModes.map((mode: ModeConfig) => ` * "${mode.name}" mode (${mode.slug}) - $ // Only include custom modes documentation if the feature is enabled if (shouldEnableCustomModeCreation) { modesContent += ` - -- Custom modes can be configured in two ways: - 1. Globally via '${customModesPath}' (created automatically on startup) - 2. Per-workspace via '.roomodes' in the workspace root directory - - When modes with the same slug exist in both files, the workspace-specific .roomodes version takes precedence. This allows projects to override global modes or define project-specific modes. - - If asked to create a project mode, create it in .roomodes in the workspace root. If asked to create a global mode, use the global custom modes file. - -- The following fields are required and must not be empty: - * slug: A valid slug (lowercase letters, numbers, and hyphens). Must be unique, and shorter is better. - * name: The display name for the mode - * roleDefinition: A detailed description of the mode's role and capabilities - * groups: Array of allowed tool groups (can be empty). Each group can be specified either as a string (e.g., "edit" to allow editing any file) or with file restrictions (e.g., ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }] to only allow editing markdown files) - -- The customInstructions field is optional. - -- For multi-line text, include newline characters in the string like "This is the first line.\\nThis is the next line.\\n\\nThis is a double line break." - -Both files should follow this structure: -{ - "customModes": [ - { - "slug": "designer", // Required: unique slug with lowercase letters, numbers, and hyphens - "name": "Designer", // Required: mode display name - "roleDefinition": "You are Roo, a UI/UX expert specializing in design systems and frontend development. Your expertise includes:\\n- Creating and maintaining design systems\\n- Implementing responsive and accessible web interfaces\\n- Working with CSS, HTML, and modern frontend frameworks\\n- Ensuring consistent user experiences across platforms", // Required: non-empty - "groups": [ // Required: array of tool groups (can be empty) - "read", // Read files group (read_file, search_files, list_files, list_code_definition_names) - "edit", // Edit files group (apply_diff, write_to_file) - allows editing any file - // Or with file restrictions: - // ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], // Edit group that only allows editing markdown files - "browser", // Browser group (browser_action) - "command", // Command group (execute_command) - "mcp" // MCP group (use_mcp_tool, access_mcp_resource) - ], - "customInstructions": "Additional instructions for the Designer mode" // Optional - } - ] -}` +If the user asks you to create or edit a new mode for this project, you can get instructions using the fetch_instructions tool, like this: + +create_mode + +` } return modesContent diff --git a/src/core/prompts/tools/fetch-instructions.ts b/src/core/prompts/tools/fetch-instructions.ts new file mode 100644 index 0000000000..eca231c562 --- /dev/null +++ b/src/core/prompts/tools/fetch-instructions.ts @@ -0,0 +1,14 @@ +export function getFetchInstructionsDescription(): string { + return `## fetch_instructions +Description: Request to fetch instructions to perform a task +Parameters: +- task: (required) The task to get instructions for. This can take the following values: + create_mcp_server + create_mode + +Example: Requesting instructions to create an MCP Server + + +create_mcp_server +` +} diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 1b9b9a43d9..408385ec12 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -1,5 +1,6 @@ import { getExecuteCommandDescription } from "./execute-command" import { getReadFileDescription } from "./read-file" +import { getFetchInstructionsDescription } from "./fetch-instructions" import { getWriteToFileDescription } from "./write-to-file" import { getSearchFilesDescription } from "./search-files" import { getListFilesDescription } from "./list-files" @@ -23,6 +24,7 @@ import { ToolArgs } from "./types" const toolDescriptionMap: Record string | undefined> = { execute_command: (args) => getExecuteCommandDescription(args), read_file: (args) => getReadFileDescription(args), + fetch_instructions: () => getFetchInstructionsDescription(), write_to_file: (args) => getWriteToFileDescription(args), search_files: (args) => getSearchFilesDescription(args), list_files: (args) => getListFilesDescription(args), @@ -97,6 +99,7 @@ export function getToolDescriptionsForMode( export { getExecuteCommandDescription, getReadFileDescription, + getFetchInstructionsDescription, getWriteToFileDescription, getSearchFilesDescription, getListFilesDescription, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 63e17ea365..12553b7c61 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -180,6 +180,7 @@ export interface ClineSayTool { | "appliedDiff" | "newFileCreated" | "readFile" + | "fetchInstructions" | "listFilesTopLevel" | "listFilesRecursive" | "listCodeDefinitionNames" diff --git a/src/shared/tool-groups.ts b/src/shared/tool-groups.ts index 11d0513e47..3cc4338394 100644 --- a/src/shared/tool-groups.ts +++ b/src/shared/tool-groups.ts @@ -8,6 +8,7 @@ export type ToolGroupConfig = { export const TOOL_DISPLAY_NAMES = { execute_command: "run commands", read_file: "read files", + fetch_instructions: "fetch instructions", write_to_file: "write files", apply_diff: "apply changes", search_files: "search files", @@ -25,7 +26,7 @@ export const TOOL_DISPLAY_NAMES = { // Define available tool groups export const TOOL_GROUPS: Record = { read: { - tools: ["read_file", "search_files", "list_files", "list_code_definition_names"], + tools: ["read_file", "fetch_instructions", "search_files", "list_files", "list_code_definition_names"], }, edit: { tools: ["apply_diff", "write_to_file", "insert_content", "search_and_replace"], diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 4e0d4432f7..f1a7ed994b 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -365,6 +365,21 @@ export const ChatRowContent = ({ ) + case "fetchInstructions": + return ( + <> +
+ {toolIcon("file-code")} + {t("chat:instructions.wantsToFetch")} +
+ + + ) case "listFilesTopLevel": return ( <> diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index e7080fd326..1d29d269fd 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -105,6 +105,9 @@ }, "current": "Actual" }, + "instructions": { + "wantsToFetch": "Roo vol obtenir instruccions detallades per ajudar amb la tasca actual." + }, "fileOperations": { "wantsToRead": "Roo vol llegir aquest fitxer:", "wantsToReadOutsideWorkspace": "Roo vol llegir aquest fitxer fora de l'espai de treball:", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 8c10441c59..5dd572796d 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -105,6 +105,9 @@ }, "current": "Aktuell" }, + "instructions": { + "wantsToFetch": "Roo möchte detaillierte Anweisungen abrufen, um bei der aktuellen Aufgabe zu helfen" + }, "fileOperations": { "wantsToRead": "Roo möchte diese Datei lesen:", "wantsToReadOutsideWorkspace": "Roo möchte diese Datei außerhalb des Arbeitsbereichs lesen:", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index ec8d4c460c..3e3e5ecfac 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -103,6 +103,9 @@ }, "current": "Current" }, + "instructions": { + "wantsToFetch": "Roo wants to fetch detailed instructions to assist with the current task" + }, "fileOperations": { "wantsToRead": "Roo wants to read this file:", "wantsToReadOutsideWorkspace": "Roo wants to read this file outside of the workspace:", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 194f8e4e7b..fdaa5a69b0 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -105,6 +105,9 @@ }, "current": "Actual" }, + "instructions": { + "wantsToFetch": "Roo quiere obtener instrucciones detalladas para ayudar con la tarea actual" + }, "fileOperations": { "wantsToRead": "Roo quiere leer este archivo:", "wantsToReadOutsideWorkspace": "Roo quiere leer este archivo fuera del espacio de trabajo:", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 0656a116bc..5aa3490b9a 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -113,6 +113,9 @@ "wantsToEditOutsideWorkspace": "Roo veut éditer ce fichier en dehors de l'espace de travail :", "wantsToCreate": "Roo veut créer un nouveau fichier :" }, + "instructions": { + "wantsToFetch": "Roo veut récupérer des instructions détaillées pour aider à la tâche actuelle" + }, "directoryOperations": { "wantsToViewTopLevel": "Roo veut voir les fichiers de premier niveau dans ce répertoire :", "didViewTopLevel": "Roo a vu les fichiers de premier niveau dans ce répertoire :", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index f29f867c80..0d00d2fd8d 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -105,6 +105,9 @@ }, "current": "वर्तमान" }, + "instructions": { + "wantsToFetch": "Roo को वर्तमान कार्य में सहायता के लिए विस्तृत निर्देश प्राप्त करना है" + }, "fileOperations": { "wantsToRead": "Roo इस फ़ाइल को पढ़ना चाहता है:", "wantsToReadOutsideWorkspace": "Roo कार्यक्षेत्र के बाहर इस फ़ाइल को पढ़ना चाहता है:", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index e82d258dbc..e21a82da0d 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -80,6 +80,9 @@ "separator": "Separatore", "edit": "Modifica...", "forNextMode": "per la prossima modalità", + "instructions": { + "wantsToFetch": "Roo vuole recuperare istruzioni dettagliate per aiutare con l'attività corrente" + }, "error": "Errore", "troubleMessage": "Roo sta avendo problemi...", "apiRequest": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 847d099ef7..9b82e56001 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -105,6 +105,9 @@ }, "current": "現在" }, + "instructions": { + "wantsToFetch": "Rooは現在のタスクを支援するための詳細な指示を取得したい" + }, "fileOperations": { "wantsToRead": "Rooはこのファイルを読みたい:", "wantsToReadOutsideWorkspace": "Rooはワークスペース外のこのファイルを読みたい:", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 9c0b01a3f3..c1b288e39b 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -105,6 +105,9 @@ }, "current": "현재" }, + "instructions": { + "wantsToFetch": "Roo는 현재 작업을 지원하기 위해 자세한 지침을 가져오려고 합니다" + }, "fileOperations": { "wantsToRead": "Roo가 이 파일을 읽고 싶어합니다:", "wantsToReadOutsideWorkspace": "Roo가 워크스페이스 외부의 이 파일을 읽고 싶어합니다:", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index ab83fd221b..846534c559 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -105,6 +105,9 @@ }, "current": "Bieżący" }, + "instructions": { + "wantsToFetch": "Roo chce pobrać szczegółowe instrukcje, aby pomóc w bieżącym zadaniu" + }, "fileOperations": { "wantsToRead": "Roo chce przeczytać ten plik:", "wantsToReadOutsideWorkspace": "Roo chce przeczytać ten plik poza obszarem roboczym:", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 96416f6f11..560e9b9bff 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -105,6 +105,9 @@ }, "current": "Atual" }, + "instructions": { + "wantsToFetch": "Roo quer buscar instruções detalhadas para ajudar com a tarefa atual" + }, "fileOperations": { "wantsToRead": "Roo quer ler este arquivo:", "wantsToReadOutsideWorkspace": "Roo quer ler este arquivo fora do espaço de trabalho:", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 1eb358ad98..b415524f92 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -105,6 +105,9 @@ }, "current": "Mevcut" }, + "instructions": { + "wantsToFetch": "Roo mevcut göreve yardımcı olmak için ayrıntılı talimatlar almak istiyor" + }, "fileOperations": { "wantsToRead": "Roo bu dosyayı okumak istiyor:", "wantsToReadOutsideWorkspace": "Roo çalışma alanı dışındaki bu dosyayı okumak istiyor:", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index fb1040bcf1..f0f3221069 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -105,6 +105,9 @@ }, "current": "Hiện tại" }, + "instructions": { + "wantsToFetch": "Roo muốn lấy hướng dẫn chi tiết để hỗ trợ nhiệm vụ hiện tại" + }, "fileOperations": { "wantsToRead": "Roo muốn đọc tệp này:", "wantsToReadOutsideWorkspace": "Roo muốn đọc tệp này bên ngoài không gian làm việc:", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index a2f8119927..be20601e9b 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -105,6 +105,9 @@ }, "current": "当前" }, + "instructions": { + "wantsToFetch": "Roo 想要获取详细指示以协助当前任务" + }, "fileOperations": { "wantsToRead": "Roo想读取此文件:", "wantsToReadOutsideWorkspace": "Roo想读取此工作区外的文件:", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 1178ec39b8..1e22877355 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -105,6 +105,9 @@ }, "current": "當前" }, + "instructions": { + "wantsToFetch": "Roo想要獲取詳細指示以協助目前任務" + }, "fileOperations": { "wantsToRead": "Roo想讀取此檔案:", "wantsToReadOutsideWorkspace": "Roo想讀取此工作區外的檔案:",