auto-coder 0.1.208__py3-none-any.whl → 0.1.211__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.208.dist-info → auto_coder-0.1.211.dist-info}/METADATA +3 -2
- {auto_coder-0.1.208.dist-info → auto_coder-0.1.211.dist-info}/RECORD +23 -19
- autocoder/chat_auto_coder.py +153 -150
- autocoder/common/buildin_tokenizer.py +37 -0
- autocoder/common/code_auto_generate.py +1 -3
- autocoder/common/code_auto_generate_diff.py +1 -3
- autocoder/common/code_auto_generate_editblock.py +1 -3
- autocoder/common/code_auto_generate_strict_diff.py +1 -3
- autocoder/common/code_modification_ranker.py +35 -17
- autocoder/common/mcp_hub.py +326 -0
- autocoder/common/mcp_server.py +83 -0
- autocoder/common/mcp_tools.py +682 -0
- autocoder/dispacher/actions/action.py +40 -21
- autocoder/rag/cache/simple_cache.py +8 -2
- autocoder/rag/loaders/docx_loader.py +3 -2
- autocoder/rag/loaders/pdf_loader.py +3 -1
- autocoder/rag/long_context_rag.py +10 -2
- autocoder/rag/utils.py +14 -9
- autocoder/version.py +1 -1
- {auto_coder-0.1.208.dist-info → auto_coder-0.1.211.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.208.dist-info → auto_coder-0.1.211.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.208.dist-info → auto_coder-0.1.211.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.208.dist-info → auto_coder-0.1.211.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Optional, Union
|
|
2
|
+
from byzerllm import prompt
|
|
3
|
+
from ..common.mcp_hub import McpHub
|
|
4
|
+
import json
|
|
5
|
+
import byzerllm
|
|
6
|
+
import re
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
from loguru import logger
|
|
9
|
+
import mcp.types as mcp_types
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class McpToolCall(BaseModel):
|
|
13
|
+
server_name: str = Field(..., description="The name of the MCP server")
|
|
14
|
+
tool_name: str = Field(..., description="The name of the tool to call")
|
|
15
|
+
arguments: Dict[str, Any] = Field(
|
|
16
|
+
default_factory=dict, description="The arguments to pass to the tool")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class McpResourceAccess(BaseModel):
|
|
20
|
+
server_name: str = Field(..., description="The name of the MCP server")
|
|
21
|
+
uri: str = Field(..., description="The URI of the resource to access")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class McpExecutor:
|
|
25
|
+
def __init__(self, mcp_hub: McpHub, llm: byzerllm.ByzerLLM):
|
|
26
|
+
self.mcp_hub = mcp_hub
|
|
27
|
+
self.llm = llm
|
|
28
|
+
|
|
29
|
+
def get_server_names(self) -> List[str]:
|
|
30
|
+
"""
|
|
31
|
+
Get the names of all connected MCP servers.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of server names
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
server_names = [server.name for server in self.mcp_hub.get_servers()]
|
|
38
|
+
return ",".join(server_names) or "(None running currently)"
|
|
39
|
+
|
|
40
|
+
def get_connected_servers_info(self) -> str:
|
|
41
|
+
"""Generate formatted information about connected MCP servers
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
mcp_hub: McpHub instance to get server information from
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Formatted string with server details
|
|
48
|
+
"""
|
|
49
|
+
servers = self.mcp_hub.get_servers()
|
|
50
|
+
if not servers:
|
|
51
|
+
return "(No MCP servers currently connected)"
|
|
52
|
+
|
|
53
|
+
info = []
|
|
54
|
+
for server in servers:
|
|
55
|
+
if server.status != "connected":
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# Format tools information
|
|
59
|
+
tools_info = []
|
|
60
|
+
if server.tools:
|
|
61
|
+
for tool in server.tools:
|
|
62
|
+
tool_str = f"- {tool.name}: {tool.description}"
|
|
63
|
+
if tool.input_schema:
|
|
64
|
+
schema_str = " Input Schema:\n" + \
|
|
65
|
+
"\n".join(f" {line}" for line in
|
|
66
|
+
json.dumps(tool.input_schema, indent=2).split("\n"))
|
|
67
|
+
tool_str += f"\n{schema_str}"
|
|
68
|
+
tools_info.append(tool_str)
|
|
69
|
+
|
|
70
|
+
# Format resource templates
|
|
71
|
+
templates_info = []
|
|
72
|
+
if server.resource_templates:
|
|
73
|
+
for template in server.resource_templates:
|
|
74
|
+
template_str = f"- {template.uri_template} ({template.name}): {template.description}"
|
|
75
|
+
templates_info.append(template_str)
|
|
76
|
+
|
|
77
|
+
# Format direct resources
|
|
78
|
+
resources_info = []
|
|
79
|
+
if server.resources:
|
|
80
|
+
for resource in server.resources:
|
|
81
|
+
resource_str = f"- {resource.uri} ({resource.name}): {resource.description}"
|
|
82
|
+
resources_info.append(resource_str)
|
|
83
|
+
|
|
84
|
+
# Parse server config
|
|
85
|
+
config = json.loads(server.config)
|
|
86
|
+
command = config['command']
|
|
87
|
+
args = config.get('args', [])
|
|
88
|
+
command_str = f"{command} {' '.join(args)}"
|
|
89
|
+
|
|
90
|
+
# Build server section
|
|
91
|
+
server_info = f"## {server.name} (`{command_str}`)"
|
|
92
|
+
if tools_info:
|
|
93
|
+
server_info += "\n\n### Available Tools\n" + \
|
|
94
|
+
"\n\n".join(tools_info)
|
|
95
|
+
if templates_info:
|
|
96
|
+
server_info += "\n\n### Resource Templates\n" + \
|
|
97
|
+
"\n".join(templates_info)
|
|
98
|
+
if resources_info:
|
|
99
|
+
server_info += "\n\n### Direct Resources\n" + \
|
|
100
|
+
"\n".join(resources_info)
|
|
101
|
+
|
|
102
|
+
info.append(server_info)
|
|
103
|
+
|
|
104
|
+
return "\n\n".join(info)
|
|
105
|
+
|
|
106
|
+
@byzerllm.prompt()
|
|
107
|
+
def mcp_prompt(self) -> str:
|
|
108
|
+
"""
|
|
109
|
+
TOOL USE
|
|
110
|
+
|
|
111
|
+
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
|
112
|
+
|
|
113
|
+
# Tool Use Formatting
|
|
114
|
+
|
|
115
|
+
Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure:
|
|
116
|
+
|
|
117
|
+
<tool_name>
|
|
118
|
+
<parameter1_name>value1</parameter1_name>
|
|
119
|
+
<parameter2_name>value2</parameter2_name>
|
|
120
|
+
...
|
|
121
|
+
</tool_name>
|
|
122
|
+
|
|
123
|
+
For example:
|
|
124
|
+
|
|
125
|
+
<read_file>
|
|
126
|
+
<path>src/main.js</path>
|
|
127
|
+
</read_file>
|
|
128
|
+
|
|
129
|
+
Always adhere to this format for the tool use to ensure proper parsing and execution.
|
|
130
|
+
|
|
131
|
+
# Tools
|
|
132
|
+
|
|
133
|
+
## use_mcp_tool
|
|
134
|
+
Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters.
|
|
135
|
+
Parameters:
|
|
136
|
+
- server_name: (required) The name of the MCP server providing the tool
|
|
137
|
+
- tool_name: (required) The name of the tool to execute
|
|
138
|
+
- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema
|
|
139
|
+
Usage:
|
|
140
|
+
<use_mcp_tool>
|
|
141
|
+
<server_name>server name here</server_name>
|
|
142
|
+
<tool_name>tool name here</tool_name>
|
|
143
|
+
<arguments>
|
|
144
|
+
{
|
|
145
|
+
"param1": "value1",
|
|
146
|
+
"param2": "value2"
|
|
147
|
+
}
|
|
148
|
+
</arguments>
|
|
149
|
+
</use_mcp_tool>
|
|
150
|
+
|
|
151
|
+
## access_mcp_resource
|
|
152
|
+
Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information.
|
|
153
|
+
Parameters:
|
|
154
|
+
- server_name: (required) The name of the MCP server providing the resource
|
|
155
|
+
- uri: (required) The URI identifying the specific resource to access
|
|
156
|
+
Usage:
|
|
157
|
+
<access_mcp_resource>
|
|
158
|
+
<server_name>server name here</server_name>
|
|
159
|
+
<uri>resource URI here</uri>
|
|
160
|
+
</access_mcp_resource>
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Tool Use Examples
|
|
164
|
+
|
|
165
|
+
## Example 1: Requesting to use an MCP tool
|
|
166
|
+
|
|
167
|
+
<use_mcp_tool>
|
|
168
|
+
<server_name>weather-server</server_name>
|
|
169
|
+
<tool_name>get_forecast</tool_name>
|
|
170
|
+
<arguments>
|
|
171
|
+
{
|
|
172
|
+
"city": "San Francisco",
|
|
173
|
+
"days": 5
|
|
174
|
+
}
|
|
175
|
+
</arguments>
|
|
176
|
+
</use_mcp_tool>
|
|
177
|
+
|
|
178
|
+
## Example 2: Requesting to access an MCP resource
|
|
179
|
+
|
|
180
|
+
<access_mcp_resource>
|
|
181
|
+
<server_name>weather-server</server_name>
|
|
182
|
+
<uri>weather://san-francisco/current</uri>
|
|
183
|
+
</access_mcp_resource>
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
====
|
|
187
|
+
|
|
188
|
+
MCP SERVERS
|
|
189
|
+
|
|
190
|
+
The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.
|
|
191
|
+
|
|
192
|
+
# Connected MCP Servers
|
|
193
|
+
|
|
194
|
+
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.
|
|
195
|
+
|
|
196
|
+
{{ connected_servers_info }}
|
|
197
|
+
|
|
198
|
+
## Creating an MCP Server
|
|
199
|
+
|
|
200
|
+
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`.
|
|
201
|
+
|
|
202
|
+
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).
|
|
203
|
+
|
|
204
|
+
Unless the user specifies otherwise, new MCP servers should be created in: ${await mcpHub.getMcpServersPath()}
|
|
205
|
+
|
|
206
|
+
### Example MCP Server
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
|
|
210
|
+
The following example demonstrates how to build an MCP server that provides weather data functionality. 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)
|
|
211
|
+
|
|
212
|
+
1. Use the `create-typescript-server` tool to bootstrap a new project in the default MCP servers directory:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
cd ${await mcpHub.getMcpServersPath()}
|
|
216
|
+
npx @modelcontextprotocol/create-server weather-server
|
|
217
|
+
cd weather-server
|
|
218
|
+
# Install dependencies
|
|
219
|
+
npm install axios
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
This will create a new project with the following structure:
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
weather-server/
|
|
226
|
+
├── package.json
|
|
227
|
+
{
|
|
228
|
+
...
|
|
229
|
+
"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)
|
|
230
|
+
"scripts": {
|
|
231
|
+
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
232
|
+
...
|
|
233
|
+
}
|
|
234
|
+
...
|
|
235
|
+
}
|
|
236
|
+
├── tsconfig.json
|
|
237
|
+
└── src/
|
|
238
|
+
└── weather-server/
|
|
239
|
+
└── index.ts # Main server implementation
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
2. Replace `src/index.ts` with the following:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
#!/usr/bin/env node
|
|
246
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
247
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
248
|
+
import {
|
|
249
|
+
CallToolRequestSchema,
|
|
250
|
+
ErrorCode,
|
|
251
|
+
ListResourcesRequestSchema,
|
|
252
|
+
ListResourceTemplatesRequestSchema,
|
|
253
|
+
ListToolsRequestSchema,
|
|
254
|
+
McpError,
|
|
255
|
+
ReadResourceRequestSchema,
|
|
256
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
257
|
+
import axios from 'axios';
|
|
258
|
+
|
|
259
|
+
const API_KEY = process.env.OPENWEATHER_API_KEY; // provided by MCP config
|
|
260
|
+
if (!API_KEY) {
|
|
261
|
+
throw new Error('OPENWEATHER_API_KEY environment variable is required');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
interface OpenWeatherResponse {
|
|
265
|
+
main: {
|
|
266
|
+
temp: number;
|
|
267
|
+
humidity: number;
|
|
268
|
+
};
|
|
269
|
+
weather: [{ description: string }];
|
|
270
|
+
wind: { speed: number };
|
|
271
|
+
dt_txt?: string;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const isValidForecastArgs = (
|
|
275
|
+
args: any
|
|
276
|
+
): args is { city: string; days?: number } =>
|
|
277
|
+
typeof args === 'object' &&
|
|
278
|
+
args !== null &&
|
|
279
|
+
typeof args.city === 'string' &&
|
|
280
|
+
(args.days === undefined || typeof args.days === 'number');
|
|
281
|
+
|
|
282
|
+
class WeatherServer {
|
|
283
|
+
private server: Server;
|
|
284
|
+
private axiosInstance;
|
|
285
|
+
|
|
286
|
+
constructor() {
|
|
287
|
+
this.server = new Server(
|
|
288
|
+
{
|
|
289
|
+
name: 'example-weather-server',
|
|
290
|
+
version: '0.1.0',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
capabilities: {
|
|
294
|
+
resources: {},
|
|
295
|
+
tools: {},
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
this.axiosInstance = axios.create({
|
|
301
|
+
baseURL: 'http://api.openweathermap.org/data/2.5',
|
|
302
|
+
params: {
|
|
303
|
+
appid: API_KEY,
|
|
304
|
+
units: 'metric',
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
this.setupResourceHandlers();
|
|
309
|
+
this.setupToolHandlers();
|
|
310
|
+
|
|
311
|
+
// Error handling
|
|
312
|
+
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
313
|
+
process.on('SIGINT', async () => {
|
|
314
|
+
await this.server.close();
|
|
315
|
+
process.exit(0);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 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]`.
|
|
320
|
+
private setupResourceHandlers() {
|
|
321
|
+
// For static resources, servers can expose a list of resources:
|
|
322
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
323
|
+
resources: [
|
|
324
|
+
// 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
|
|
325
|
+
{
|
|
326
|
+
uri: `weather://San Francisco/current`, // Unique identifier for San Francisco weather resource
|
|
327
|
+
name: `Current weather in San Francisco`, // Human-readable name
|
|
328
|
+
mimeType: 'application/json', // Optional MIME type
|
|
329
|
+
// Optional description
|
|
330
|
+
description:
|
|
331
|
+
'Real-time weather data for San Francisco including temperature, conditions, humidity, and wind speed',
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
}));
|
|
335
|
+
|
|
336
|
+
// For dynamic resources, servers can expose resource templates:
|
|
337
|
+
this.server.setRequestHandler(
|
|
338
|
+
ListResourceTemplatesRequestSchema,
|
|
339
|
+
async () => ({
|
|
340
|
+
resourceTemplates: [
|
|
341
|
+
{
|
|
342
|
+
uriTemplate: 'weather://{city}/current', // URI template (RFC 6570)
|
|
343
|
+
name: 'Current weather for a given city', // Human-readable name
|
|
344
|
+
mimeType: 'application/json', // Optional MIME type
|
|
345
|
+
description: 'Real-time weather data for a specified city', // Optional description
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// ReadResourceRequestSchema is used for both static resources and dynamic resource templates
|
|
352
|
+
this.server.setRequestHandler(
|
|
353
|
+
ReadResourceRequestSchema,
|
|
354
|
+
async (request) => {
|
|
355
|
+
const match = request.params.uri.match(
|
|
356
|
+
/^weather:\/\/([^/]+)\/current$/
|
|
357
|
+
);
|
|
358
|
+
if (!match) {
|
|
359
|
+
throw new McpError(
|
|
360
|
+
ErrorCode.InvalidRequest,
|
|
361
|
+
`Invalid URI format: ${request.params.uri}`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const city = decodeURIComponent(match[1]);
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const response = await this.axiosInstance.get(
|
|
368
|
+
'weather', // current weather
|
|
369
|
+
{
|
|
370
|
+
params: { q: city },
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
contents: [
|
|
376
|
+
{
|
|
377
|
+
uri: request.params.uri,
|
|
378
|
+
mimeType: 'application/json',
|
|
379
|
+
text: JSON.stringify(
|
|
380
|
+
{
|
|
381
|
+
temperature: response.data.main.temp,
|
|
382
|
+
conditions: response.data.weather[0].description,
|
|
383
|
+
humidity: response.data.main.humidity,
|
|
384
|
+
wind_speed: response.data.wind.speed,
|
|
385
|
+
timestamp: new Date().toISOString(),
|
|
386
|
+
},
|
|
387
|
+
null,
|
|
388
|
+
2
|
|
389
|
+
),
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (axios.isAxiosError(error)) {
|
|
395
|
+
throw new McpError(
|
|
396
|
+
ErrorCode.InternalError,
|
|
397
|
+
`Weather API error: ${
|
|
398
|
+
error.response?.data.message ?? error.message
|
|
399
|
+
}`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* 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.
|
|
409
|
+
* - 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.
|
|
410
|
+
* - While resources and tools are similar, you should prefer to create tools over resources when possible as they provide more flexibility.
|
|
411
|
+
*/
|
|
412
|
+
private setupToolHandlers() {
|
|
413
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
414
|
+
tools: [
|
|
415
|
+
{
|
|
416
|
+
name: 'get_forecast', // Unique identifier
|
|
417
|
+
description: 'Get weather forecast for a city', // Human-readable description
|
|
418
|
+
inputSchema: {
|
|
419
|
+
// JSON Schema for parameters
|
|
420
|
+
type: 'object',
|
|
421
|
+
properties: {
|
|
422
|
+
city: {
|
|
423
|
+
type: 'string',
|
|
424
|
+
description: 'City name',
|
|
425
|
+
},
|
|
426
|
+
days: {
|
|
427
|
+
type: 'number',
|
|
428
|
+
description: 'Number of days (1-5)',
|
|
429
|
+
minimum: 1,
|
|
430
|
+
maximum: 5,
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
required: ['city'], // Array of required property names
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
}));
|
|
438
|
+
|
|
439
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
440
|
+
if (request.params.name !== 'get_forecast') {
|
|
441
|
+
throw new McpError(
|
|
442
|
+
ErrorCode.MethodNotFound,
|
|
443
|
+
`Unknown tool: ${request.params.name}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (!isValidForecastArgs(request.params.arguments)) {
|
|
448
|
+
throw new McpError(
|
|
449
|
+
ErrorCode.InvalidParams,
|
|
450
|
+
'Invalid forecast arguments'
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const city = request.params.arguments.city;
|
|
455
|
+
const days = Math.min(request.params.arguments.days || 3, 5);
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
const response = await this.axiosInstance.get<{
|
|
459
|
+
list: OpenWeatherResponse[];
|
|
460
|
+
}>('forecast', {
|
|
461
|
+
params: {
|
|
462
|
+
q: city,
|
|
463
|
+
cnt: days * 8,
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
content: [
|
|
469
|
+
{
|
|
470
|
+
type: 'text',
|
|
471
|
+
text: JSON.stringify(response.data.list, null, 2),
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
};
|
|
475
|
+
} catch (error) {
|
|
476
|
+
if (axios.isAxiosError(error)) {
|
|
477
|
+
return {
|
|
478
|
+
content: [
|
|
479
|
+
{
|
|
480
|
+
type: 'text',
|
|
481
|
+
text: `Weather API error: ${
|
|
482
|
+
error.response?.data.message ?? error.message
|
|
483
|
+
}`,
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
isError: true,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async run() {
|
|
495
|
+
const transport = new StdioServerTransport();
|
|
496
|
+
await this.server.connect(transport);
|
|
497
|
+
console.error('Weather MCP server running on stdio');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const server = new WeatherServer();
|
|
502
|
+
server.run().catch(console.error);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
(Remember: This is just an example–you may use different dependencies, break the implementation up into multiple files, etc.)
|
|
506
|
+
|
|
507
|
+
3. Build and compile the executable JavaScript file
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
npm run build
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
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.
|
|
514
|
+
|
|
515
|
+
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.
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"mcpServers": {
|
|
520
|
+
...,
|
|
521
|
+
"weather": {
|
|
522
|
+
"command": "node",
|
|
523
|
+
"args": ["/path/to/weather-server/build/index.js"],
|
|
524
|
+
"env": {
|
|
525
|
+
"OPENWEATHER_API_KEY": "user-provided-api-key"
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
(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.)
|
|
533
|
+
|
|
534
|
+
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.
|
|
535
|
+
|
|
536
|
+
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?"
|
|
537
|
+
|
|
538
|
+
## Editing MCP Servers
|
|
539
|
+
|
|
540
|
+
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: {{ server_names }}, 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 replace_in_file to make changes to the files.
|
|
541
|
+
|
|
542
|
+
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.
|
|
543
|
+
|
|
544
|
+
# MCP Servers Are Not Always Necessary
|
|
545
|
+
|
|
546
|
+
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...").
|
|
547
|
+
|
|
548
|
+
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.
|
|
549
|
+
"""
|
|
550
|
+
return {
|
|
551
|
+
"connected_servers_info": self.get_connected_servers_info(),
|
|
552
|
+
"server_names": self.get_server_names()
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async def extract_mcp_calls(self,content: str) -> List[Union[McpToolCall, McpResourceAccess]]:
|
|
556
|
+
"""
|
|
557
|
+
Extract MCP tool calls and resource accesses from content.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
content: The content to parse for MCP tool calls
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
List of McpToolCall and McpResourceAccess objects
|
|
564
|
+
"""
|
|
565
|
+
results = []
|
|
566
|
+
|
|
567
|
+
# Regex pattern to match tool calls
|
|
568
|
+
tool_pattern = re.compile(
|
|
569
|
+
r"<use_mcp_tool>.*?<server_name>(.*?)</server_name>.*?"
|
|
570
|
+
r"<tool_name>(.*?)</tool_name>.*?"
|
|
571
|
+
r"<arguments>(.*?)</arguments>.*?</use_mcp_tool>",
|
|
572
|
+
re.DOTALL
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# Regex pattern to match resource accesses
|
|
576
|
+
resource_pattern = re.compile(
|
|
577
|
+
r"<access_mcp_resource>.*?<server_name>(.*?)</server_name>.*?"
|
|
578
|
+
r"<uri>(.*?)</uri>.*?</access_mcp_resource>",
|
|
579
|
+
re.DOTALL
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Extract tool calls
|
|
583
|
+
for match in tool_pattern.finditer(content):
|
|
584
|
+
try:
|
|
585
|
+
arguments = json.loads(match.group(3).strip())
|
|
586
|
+
results.append(McpToolCall(
|
|
587
|
+
server_name=match.group(1).strip(),
|
|
588
|
+
tool_name=match.group(2).strip(),
|
|
589
|
+
arguments=arguments
|
|
590
|
+
))
|
|
591
|
+
except json.JSONDecodeError:
|
|
592
|
+
continue
|
|
593
|
+
|
|
594
|
+
# Extract resource accesses
|
|
595
|
+
for match in resource_pattern.finditer(content):
|
|
596
|
+
results.append(McpResourceAccess(
|
|
597
|
+
server_name=match.group(1).strip(),
|
|
598
|
+
uri=match.group(2).strip()
|
|
599
|
+
))
|
|
600
|
+
|
|
601
|
+
return results
|
|
602
|
+
|
|
603
|
+
async def run(self, conversations: List[Dict[str, Any]]):
|
|
604
|
+
new_conversations = [{
|
|
605
|
+
"role": "user",
|
|
606
|
+
"content": self.mcp_prompt.prompt()
|
|
607
|
+
}, {
|
|
608
|
+
"role": "assistant",
|
|
609
|
+
"content": "I have read the tools usage instructions."
|
|
610
|
+
}] + conversations
|
|
611
|
+
|
|
612
|
+
all_tool_results = []
|
|
613
|
+
|
|
614
|
+
v = self.llm.chat_oai(conversations=new_conversations)
|
|
615
|
+
content = v[0].output
|
|
616
|
+
tools = await self.extract_mcp_calls(content)
|
|
617
|
+
# while tools:
|
|
618
|
+
# results = await self.execute_mcp_tools(tools)
|
|
619
|
+
# all_tool_results += results
|
|
620
|
+
# str_results = "\n\n".join(self.format_mcp_result(result) for result in results)
|
|
621
|
+
# new_conversations += [{
|
|
622
|
+
# "role": "assistant",
|
|
623
|
+
# "content": f"Tool results: {str_results}"
|
|
624
|
+
# },{
|
|
625
|
+
# "role": "user",
|
|
626
|
+
# "content": "continue"
|
|
627
|
+
# }]
|
|
628
|
+
# v = self.llm.chat_oai(conversations=new_conversations)
|
|
629
|
+
# content = v[0].output
|
|
630
|
+
# tools = await self.extract_mcp_calls(content)
|
|
631
|
+
if tools:
|
|
632
|
+
results = await self.execute_mcp_tools(tools)
|
|
633
|
+
all_tool_results += results
|
|
634
|
+
|
|
635
|
+
return new_conversations,all_tool_results
|
|
636
|
+
|
|
637
|
+
def format_mcp_result(self, result: Any) -> str:
|
|
638
|
+
"""
|
|
639
|
+
Format MCP tool or resource result into a human-readable string.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
result: The result from MCP tool call or resource access
|
|
643
|
+
|
|
644
|
+
Returns:
|
|
645
|
+
Formatted string representation of the result
|
|
646
|
+
"""
|
|
647
|
+
if result is None:
|
|
648
|
+
return "(No result)"
|
|
649
|
+
# if isinstance(result, mcp_types.CallToolResult):
|
|
650
|
+
# for content in result.contents:
|
|
651
|
+
# if isinstance(content, mcp_types.TextContent):
|
|
652
|
+
# return content.text
|
|
653
|
+
# if isinstance(result, mcp_types.ReadResourceResult):
|
|
654
|
+
# return result.contents
|
|
655
|
+
return json.dumps(result.model_dump(), indent=2, ensure_ascii=False)
|
|
656
|
+
|
|
657
|
+
async def execute_mcp_tools(self, tools: List[Union[McpToolCall, McpResourceAccess]]) -> List[Any]:
|
|
658
|
+
"""
|
|
659
|
+
Execute MCP tools and return results in order.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
mcp_hub: McpHub instance to execute tools
|
|
663
|
+
tools: List of McpToolCall and McpResourceAccess objects
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
List of results in the same order as the input tools
|
|
667
|
+
"""
|
|
668
|
+
results = []
|
|
669
|
+
for tool in tools:
|
|
670
|
+
try:
|
|
671
|
+
if isinstance(tool, McpToolCall):
|
|
672
|
+
result = await self.mcp_hub.call_tool(tool.server_name, tool.tool_name, tool.arguments)
|
|
673
|
+
results.append(result)
|
|
674
|
+
elif isinstance(tool, McpResourceAccess):
|
|
675
|
+
result = await self.mcp_hub.read_resource(tool.server_name, tool.uri)
|
|
676
|
+
results.append(result)
|
|
677
|
+
else:
|
|
678
|
+
results.append(None)
|
|
679
|
+
except Exception as e:
|
|
680
|
+
logger.error(f"Failed to execute MCP tool {tool}: {e}")
|
|
681
|
+
results.append(None)
|
|
682
|
+
return results
|