@musashishao/agent-kit 1.0.0 → 1.0.1
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 @musashishao/agent-kit might be problematic. Click here for more details.
- package/.agent/ARCHITECTURE.md +4 -2
- package/.agent/skills/mcp-builder/SKILL.md +583 -97
- package/.agent/skills/mcp-builder/python-template.md +522 -0
- package/.agent/skills/mcp-builder/tool-patterns.md +642 -0
- package/.agent/skills/mcp-builder/typescript-template.md +361 -0
- package/.agent/skills/problem-solving/SKILL.md +556 -0
- package/.agent/skills/problem-solving/collision-zone-thinking.md +285 -0
- package/.agent/skills/problem-solving/inversion-exercise.md +205 -0
- package/.agent/skills/problem-solving/meta-pattern-recognition.md +313 -0
- package/.agent/skills/problem-solving/scale-game.md +300 -0
- package/.agent/skills/problem-solving/simplification-cascades.md +321 -0
- package/.agent/skills/problem-solving/when-stuck.md +146 -0
- package/package.json +2 -2
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mcp-builder
|
|
3
|
-
description: MCP (Model Context Protocol) server building
|
|
4
|
-
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
3
|
+
description: MCP (Model Context Protocol) server building. Complete guide with TypeScript/Python examples, FastMCP, tool design, deployment patterns.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
|
|
5
|
+
skills:
|
|
6
|
+
- typescript-expert
|
|
7
|
+
- python-patterns
|
|
5
8
|
---
|
|
6
9
|
|
|
7
10
|
# MCP Builder
|
|
8
11
|
|
|
9
|
-
>
|
|
12
|
+
> Build production-ready MCP servers to extend AI capabilities with external tools and data.
|
|
13
|
+
|
|
14
|
+
## 🔧 Quick Reference
|
|
15
|
+
|
|
16
|
+
| File | Purpose |
|
|
17
|
+
|------|---------|
|
|
18
|
+
| [typescript-template.md](typescript-template.md) | TypeScript MCP server template |
|
|
19
|
+
| [python-template.md](python-template.md) | FastMCP Python template |
|
|
20
|
+
| [tool-patterns.md](tool-patterns.md) | Tool design patterns & examples |
|
|
10
21
|
|
|
11
22
|
---
|
|
12
23
|
|
|
@@ -14,154 +25,628 @@ allowed-tools: Read, Write, Edit, Glob, Grep
|
|
|
14
25
|
|
|
15
26
|
### What is MCP?
|
|
16
27
|
|
|
17
|
-
Model Context Protocol - standard for connecting AI systems with external tools
|
|
28
|
+
Model Context Protocol - the open standard for connecting AI systems with external tools, data sources, and services.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────┐ MCP Protocol ┌─────────────────┐
|
|
32
|
+
│ AI Host │◄───────────────────►│ MCP Server │
|
|
33
|
+
│ (Claude) │ Tools/Resources │ (Your Service) │
|
|
34
|
+
└─────────────┘ └─────────────────┘
|
|
35
|
+
```
|
|
18
36
|
|
|
19
37
|
### Core Concepts
|
|
20
38
|
|
|
21
|
-
| Concept | Purpose |
|
|
22
|
-
|
|
23
|
-
| **Tools** | Functions AI can call |
|
|
24
|
-
| **Resources** | Data AI can read |
|
|
25
|
-
| **Prompts** | Pre-defined
|
|
39
|
+
| Concept | Purpose | Example |
|
|
40
|
+
|---------|---------|---------|
|
|
41
|
+
| **Tools** | Functions AI can call | `get_weather`, `create_issue` |
|
|
42
|
+
| **Resources** | Data AI can read | Files, database records, APIs |
|
|
43
|
+
| **Prompts** | Pre-defined templates | Workflow shortcuts |
|
|
44
|
+
|
|
45
|
+
### Why Build MCP Servers?
|
|
46
|
+
|
|
47
|
+
| Use Case | Example |
|
|
48
|
+
|----------|---------|
|
|
49
|
+
| **API Integration** | Connect to GitHub, Slack, Notion |
|
|
50
|
+
| **Database Access** | Query PostgreSQL, MongoDB |
|
|
51
|
+
| **File Operations** | Read/write local files |
|
|
52
|
+
| **Custom Tools** | Domain-specific automation |
|
|
26
53
|
|
|
27
54
|
---
|
|
28
55
|
|
|
29
|
-
## 2. Server
|
|
56
|
+
## 2. TypeScript MCP Server
|
|
30
57
|
|
|
31
|
-
###
|
|
58
|
+
### Quick Start
|
|
32
59
|
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
60
|
+
```bash
|
|
61
|
+
# Create project
|
|
62
|
+
mkdir my-mcp-server && cd my-mcp-server
|
|
63
|
+
npm init -y
|
|
64
|
+
npm install @modelcontextprotocol/sdk zod
|
|
65
|
+
npm install -D typescript @types/node tsx
|
|
66
|
+
|
|
67
|
+
# Create structure
|
|
68
|
+
mkdir src
|
|
69
|
+
touch src/index.ts
|
|
39
70
|
```
|
|
40
71
|
|
|
41
|
-
###
|
|
72
|
+
### Basic Server Template
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// src/index.ts
|
|
76
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
77
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
78
|
+
import { z } from "zod";
|
|
79
|
+
|
|
80
|
+
// Create server instance
|
|
81
|
+
const server = new McpServer({
|
|
82
|
+
name: "my-mcp-server",
|
|
83
|
+
version: "1.0.0",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Define a tool
|
|
87
|
+
server.tool(
|
|
88
|
+
"greet",
|
|
89
|
+
"Greet a user by name",
|
|
90
|
+
{
|
|
91
|
+
name: z.string().describe("The user's name"),
|
|
92
|
+
},
|
|
93
|
+
async ({ name }) => {
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `Hello, ${name}! Welcome to MCP.`,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Define a resource
|
|
106
|
+
server.resource(
|
|
107
|
+
"status",
|
|
108
|
+
"status://server",
|
|
109
|
+
async (uri) => ({
|
|
110
|
+
contents: [
|
|
111
|
+
{
|
|
112
|
+
uri: uri.href,
|
|
113
|
+
mimeType: "application/json",
|
|
114
|
+
text: JSON.stringify({ status: "running", timestamp: new Date().toISOString() }),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Start server
|
|
121
|
+
async function main() {
|
|
122
|
+
const transport = new StdioServerTransport();
|
|
123
|
+
await server.connect(transport);
|
|
124
|
+
console.error("MCP Server running on stdio");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main().catch(console.error);
|
|
128
|
+
```
|
|
42
129
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
130
|
+
### Package.json Config
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"name": "my-mcp-server",
|
|
135
|
+
"version": "1.0.0",
|
|
136
|
+
"type": "module",
|
|
137
|
+
"bin": {
|
|
138
|
+
"my-mcp-server": "./dist/index.js"
|
|
139
|
+
},
|
|
140
|
+
"scripts": {
|
|
141
|
+
"build": "tsc",
|
|
142
|
+
"dev": "tsx src/index.ts",
|
|
143
|
+
"start": "node dist/index.js"
|
|
144
|
+
},
|
|
145
|
+
"dependencies": {
|
|
146
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
147
|
+
"zod": "^3.23.0"
|
|
148
|
+
},
|
|
149
|
+
"devDependencies": {
|
|
150
|
+
"@types/node": "^20.0.0",
|
|
151
|
+
"tsx": "^4.0.0",
|
|
152
|
+
"typescript": "^5.0.0"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
48
156
|
|
|
49
157
|
---
|
|
50
158
|
|
|
51
|
-
## 3.
|
|
159
|
+
## 3. Python MCP Server (FastMCP)
|
|
52
160
|
|
|
53
|
-
###
|
|
161
|
+
### Quick Start
|
|
54
162
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
| Single purpose | One thing well |
|
|
59
|
-
| Validated input | Schema with types and descriptions |
|
|
60
|
-
| Structured output | Predictable response format |
|
|
163
|
+
```bash
|
|
164
|
+
# Install FastMCP
|
|
165
|
+
pip install fastmcp
|
|
61
166
|
|
|
62
|
-
|
|
167
|
+
# Or with uv (recommended)
|
|
168
|
+
uv add fastmcp
|
|
169
|
+
```
|
|
63
170
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
171
|
+
### Basic Server Template
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# server.py
|
|
175
|
+
from fastmcp import FastMCP
|
|
176
|
+
|
|
177
|
+
# Create server
|
|
178
|
+
mcp = FastMCP("My MCP Server")
|
|
179
|
+
|
|
180
|
+
# Define a tool
|
|
181
|
+
@mcp.tool()
|
|
182
|
+
def greet(name: str) -> str:
|
|
183
|
+
"""Greet a user by name.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
name: The user's name
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
A greeting message
|
|
190
|
+
"""
|
|
191
|
+
return f"Hello, {name}! Welcome to MCP."
|
|
192
|
+
|
|
193
|
+
# Define a tool with complex input
|
|
194
|
+
@mcp.tool()
|
|
195
|
+
def calculate(
|
|
196
|
+
operation: str,
|
|
197
|
+
a: float,
|
|
198
|
+
b: float
|
|
199
|
+
) -> dict:
|
|
200
|
+
"""Perform a mathematical calculation.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
operation: One of 'add', 'subtract', 'multiply', 'divide'
|
|
204
|
+
a: First number
|
|
205
|
+
b: Second number
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Result of the calculation
|
|
209
|
+
"""
|
|
210
|
+
ops = {
|
|
211
|
+
"add": lambda: a + b,
|
|
212
|
+
"subtract": lambda: a - b,
|
|
213
|
+
"multiply": lambda: a * b,
|
|
214
|
+
"divide": lambda: a / b if b != 0 else "Error: Division by zero",
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if operation not in ops:
|
|
218
|
+
return {"error": f"Unknown operation: {operation}"}
|
|
219
|
+
|
|
220
|
+
return {"result": ops[operation](), "operation": operation}
|
|
221
|
+
|
|
222
|
+
# Define a resource
|
|
223
|
+
@mcp.resource("config://settings")
|
|
224
|
+
def get_settings() -> str:
|
|
225
|
+
"""Get server configuration."""
|
|
226
|
+
import json
|
|
227
|
+
return json.dumps({
|
|
228
|
+
"version": "1.0.0",
|
|
229
|
+
"features": ["tools", "resources"],
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
# Define a prompt template
|
|
233
|
+
@mcp.prompt()
|
|
234
|
+
def code_review(code: str, language: str = "python") -> str:
|
|
235
|
+
"""Generate a code review prompt."""
|
|
236
|
+
return f"""Please review this {language} code:
|
|
237
|
+
|
|
238
|
+
```{language}
|
|
239
|
+
{code}
|
|
240
|
+
```
|
|
70
241
|
|
|
71
|
-
|
|
242
|
+
Focus on:
|
|
243
|
+
1. Code quality
|
|
244
|
+
2. Potential bugs
|
|
245
|
+
3. Performance
|
|
246
|
+
4. Best practices"""
|
|
72
247
|
|
|
73
|
-
|
|
248
|
+
if __name__ == "__main__":
|
|
249
|
+
mcp.run()
|
|
250
|
+
```
|
|
74
251
|
|
|
75
|
-
###
|
|
252
|
+
### Running FastMCP
|
|
76
253
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
| Dynamic | Generated on request |
|
|
81
|
-
| Template | URI with parameters |
|
|
254
|
+
```bash
|
|
255
|
+
# Development
|
|
256
|
+
fastmcp dev server.py
|
|
82
257
|
|
|
83
|
-
|
|
258
|
+
# Production
|
|
259
|
+
fastmcp run server.py
|
|
84
260
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
| Parameterized | `users://{userId}` |
|
|
89
|
-
| Collection | `files://project/*` |
|
|
261
|
+
# Install to Claude Desktop
|
|
262
|
+
fastmcp install server.py --name "My Server"
|
|
263
|
+
```
|
|
90
264
|
|
|
91
265
|
---
|
|
92
266
|
|
|
93
|
-
##
|
|
267
|
+
## 4. Tool Design Patterns
|
|
268
|
+
|
|
269
|
+
### Pattern 1: CRUD Operations
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
@mcp.tool()
|
|
273
|
+
def create_task(title: str, description: str = "", priority: str = "medium") -> dict:
|
|
274
|
+
"""Create a new task."""
|
|
275
|
+
task = {
|
|
276
|
+
"id": generate_id(),
|
|
277
|
+
"title": title,
|
|
278
|
+
"description": description,
|
|
279
|
+
"priority": priority,
|
|
280
|
+
"status": "pending",
|
|
281
|
+
"created_at": datetime.now().isoformat(),
|
|
282
|
+
}
|
|
283
|
+
save_task(task)
|
|
284
|
+
return {"success": True, "task": task}
|
|
285
|
+
|
|
286
|
+
@mcp.tool()
|
|
287
|
+
def get_task(task_id: str) -> dict:
|
|
288
|
+
"""Get task by ID."""
|
|
289
|
+
task = load_task(task_id)
|
|
290
|
+
if not task:
|
|
291
|
+
return {"error": f"Task {task_id} not found"}
|
|
292
|
+
return task
|
|
293
|
+
|
|
294
|
+
@mcp.tool()
|
|
295
|
+
def update_task(task_id: str, status: str = None, priority: str = None) -> dict:
|
|
296
|
+
"""Update task properties."""
|
|
297
|
+
# Implementation...
|
|
298
|
+
|
|
299
|
+
@mcp.tool()
|
|
300
|
+
def delete_task(task_id: str) -> dict:
|
|
301
|
+
"""Delete a task."""
|
|
302
|
+
# Implementation...
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Pattern 2: API Integration
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
import httpx
|
|
309
|
+
|
|
310
|
+
@mcp.tool()
|
|
311
|
+
async def github_create_issue(
|
|
312
|
+
repo: str,
|
|
313
|
+
title: str,
|
|
314
|
+
body: str = "",
|
|
315
|
+
labels: list[str] = []
|
|
316
|
+
) -> dict:
|
|
317
|
+
"""Create a GitHub issue.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
repo: Repository in format 'owner/repo'
|
|
321
|
+
title: Issue title
|
|
322
|
+
body: Issue body (markdown)
|
|
323
|
+
labels: List of label names
|
|
324
|
+
"""
|
|
325
|
+
token = os.environ.get("GITHUB_TOKEN")
|
|
326
|
+
if not token:
|
|
327
|
+
return {"error": "GITHUB_TOKEN not set"}
|
|
328
|
+
|
|
329
|
+
async with httpx.AsyncClient() as client:
|
|
330
|
+
response = await client.post(
|
|
331
|
+
f"https://api.github.com/repos/{repo}/issues",
|
|
332
|
+
headers={
|
|
333
|
+
"Authorization": f"token {token}",
|
|
334
|
+
"Accept": "application/vnd.github.v3+json",
|
|
335
|
+
},
|
|
336
|
+
json={
|
|
337
|
+
"title": title,
|
|
338
|
+
"body": body,
|
|
339
|
+
"labels": labels,
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if response.status_code == 201:
|
|
344
|
+
issue = response.json()
|
|
345
|
+
return {
|
|
346
|
+
"success": True,
|
|
347
|
+
"issue_number": issue["number"],
|
|
348
|
+
"url": issue["html_url"],
|
|
349
|
+
}
|
|
350
|
+
else:
|
|
351
|
+
return {"error": response.text}
|
|
352
|
+
```
|
|
94
353
|
|
|
95
|
-
###
|
|
354
|
+
### Pattern 3: Database Query
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
import sqlite3
|
|
358
|
+
|
|
359
|
+
@mcp.tool()
|
|
360
|
+
def query_database(sql: str, params: list = []) -> dict:
|
|
361
|
+
"""Execute a read-only SQL query.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
sql: SQL SELECT query
|
|
365
|
+
params: Query parameters (for safety)
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Query results as list of dicts
|
|
369
|
+
"""
|
|
370
|
+
# Validate read-only
|
|
371
|
+
if not sql.strip().upper().startswith("SELECT"):
|
|
372
|
+
return {"error": "Only SELECT queries allowed"}
|
|
373
|
+
|
|
374
|
+
conn = sqlite3.connect("database.db")
|
|
375
|
+
conn.row_factory = sqlite3.Row
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
cursor = conn.execute(sql, params)
|
|
379
|
+
rows = [dict(row) for row in cursor.fetchall()]
|
|
380
|
+
return {"rows": rows, "count": len(rows)}
|
|
381
|
+
except Exception as e:
|
|
382
|
+
return {"error": str(e)}
|
|
383
|
+
finally:
|
|
384
|
+
conn.close()
|
|
385
|
+
```
|
|
96
386
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
387
|
+
### Pattern 4: File Operations
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
from pathlib import Path
|
|
391
|
+
|
|
392
|
+
@mcp.tool()
|
|
393
|
+
def read_file(path: str) -> dict:
|
|
394
|
+
"""Read a file's contents.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
path: Relative path to file
|
|
398
|
+
"""
|
|
399
|
+
file_path = Path(path).resolve()
|
|
400
|
+
|
|
401
|
+
# Security: validate path
|
|
402
|
+
if not file_path.is_relative_to(ALLOWED_DIR):
|
|
403
|
+
return {"error": "Access denied"}
|
|
404
|
+
|
|
405
|
+
if not file_path.exists():
|
|
406
|
+
return {"error": f"File not found: {path}"}
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
"content": file_path.read_text(),
|
|
410
|
+
"size": file_path.stat().st_size,
|
|
411
|
+
"modified": file_path.stat().st_mtime,
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@mcp.tool()
|
|
415
|
+
def write_file(path: str, content: str) -> dict:
|
|
416
|
+
"""Write content to a file.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
path: Relative path to file
|
|
420
|
+
content: Content to write
|
|
421
|
+
"""
|
|
422
|
+
file_path = Path(path).resolve()
|
|
423
|
+
|
|
424
|
+
if not file_path.is_relative_to(ALLOWED_DIR):
|
|
425
|
+
return {"error": "Access denied"}
|
|
426
|
+
|
|
427
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
428
|
+
file_path.write_text(content)
|
|
429
|
+
|
|
430
|
+
return {"success": True, "path": str(file_path)}
|
|
431
|
+
```
|
|
102
432
|
|
|
103
|
-
|
|
433
|
+
---
|
|
104
434
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
435
|
+
## 5. Claude Desktop Configuration
|
|
436
|
+
|
|
437
|
+
### Config Location
|
|
438
|
+
|
|
439
|
+
| OS | Path |
|
|
440
|
+
|----|------|
|
|
441
|
+
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
442
|
+
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
443
|
+
|
|
444
|
+
### Config Example
|
|
445
|
+
|
|
446
|
+
```json
|
|
447
|
+
{
|
|
448
|
+
"mcpServers": {
|
|
449
|
+
"my-server": {
|
|
450
|
+
"command": "node",
|
|
451
|
+
"args": ["/path/to/my-mcp-server/dist/index.js"],
|
|
452
|
+
"env": {
|
|
453
|
+
"API_KEY": "your-api-key"
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
"python-server": {
|
|
457
|
+
"command": "python",
|
|
458
|
+
"args": ["/path/to/server.py"],
|
|
459
|
+
"env": {
|
|
460
|
+
"DATABASE_URL": "postgresql://..."
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
"fastmcp-server": {
|
|
464
|
+
"command": "fastmcp",
|
|
465
|
+
"args": ["run", "/path/to/server.py"]
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
```
|
|
109
470
|
|
|
110
471
|
---
|
|
111
472
|
|
|
112
|
-
## 6.
|
|
473
|
+
## 6. Best Practices
|
|
113
474
|
|
|
114
|
-
###
|
|
475
|
+
### Tool Naming
|
|
115
476
|
|
|
116
|
-
|
|
|
117
|
-
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
477
|
+
| ❌ Bad | ✅ Good | Why |
|
|
478
|
+
|--------|---------|-----|
|
|
479
|
+
| `do_thing` | `create_user` | Specific action |
|
|
480
|
+
| `process` | `parse_csv` | Clear purpose |
|
|
481
|
+
| `handle_request` | `send_email` | Verb + noun |
|
|
482
|
+
|
|
483
|
+
### Input Validation
|
|
484
|
+
|
|
485
|
+
```python
|
|
486
|
+
@mcp.tool()
|
|
487
|
+
def send_email(
|
|
488
|
+
to: str,
|
|
489
|
+
subject: str,
|
|
490
|
+
body: str
|
|
491
|
+
) -> dict:
|
|
492
|
+
"""Send an email."""
|
|
493
|
+
# Validate email format
|
|
494
|
+
if not re.match(r"[^@]+@[^@]+\.[^@]+", to):
|
|
495
|
+
return {"error": "Invalid email format"}
|
|
496
|
+
|
|
497
|
+
# Validate subject length
|
|
498
|
+
if len(subject) > 200:
|
|
499
|
+
return {"error": "Subject too long (max 200 chars)"}
|
|
500
|
+
|
|
501
|
+
# ... send email
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Error Handling
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
@mcp.tool()
|
|
508
|
+
def risky_operation(param: str) -> dict:
|
|
509
|
+
"""Operation that might fail."""
|
|
510
|
+
try:
|
|
511
|
+
result = do_something(param)
|
|
512
|
+
return {"success": True, "result": result}
|
|
513
|
+
except ValidationError as e:
|
|
514
|
+
return {"error": f"Invalid input: {e.message}"}
|
|
515
|
+
except PermissionError:
|
|
516
|
+
return {"error": "Permission denied"}
|
|
517
|
+
except Exception as e:
|
|
518
|
+
# Log internally, return generic message
|
|
519
|
+
logging.error(f"Unexpected error: {e}")
|
|
520
|
+
return {"error": "An unexpected error occurred"}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Security Checklist
|
|
524
|
+
|
|
525
|
+
- [ ] Validate all inputs
|
|
526
|
+
- [ ] Use environment variables for secrets
|
|
527
|
+
- [ ] Limit file system access
|
|
528
|
+
- [ ] Sanitize SQL queries (use parameters)
|
|
529
|
+
- [ ] Rate limit expensive operations
|
|
530
|
+
- [ ] Log operations for audit
|
|
121
531
|
|
|
122
532
|
---
|
|
123
533
|
|
|
124
|
-
## 7.
|
|
534
|
+
## 7. Deployment Options
|
|
125
535
|
|
|
126
|
-
###
|
|
536
|
+
### Local (Claude Desktop)
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
# Build and configure
|
|
540
|
+
npm run build
|
|
541
|
+
# Add to claude_desktop_config.json
|
|
542
|
+
```
|
|
127
543
|
|
|
128
|
-
|
|
129
|
-
- Sanitize user-provided data
|
|
130
|
-
- Limit resource access
|
|
544
|
+
### NPX Distribution
|
|
131
545
|
|
|
132
|
-
|
|
546
|
+
```json
|
|
547
|
+
// package.json
|
|
548
|
+
{
|
|
549
|
+
"name": "@myorg/mcp-server",
|
|
550
|
+
"bin": {
|
|
551
|
+
"my-mcp-server": "./dist/index.js"
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
```
|
|
133
555
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
556
|
+
```bash
|
|
557
|
+
# Publish
|
|
558
|
+
npm publish --access public
|
|
559
|
+
|
|
560
|
+
# Users can run
|
|
561
|
+
npx @myorg/mcp-server
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Docker
|
|
565
|
+
|
|
566
|
+
```dockerfile
|
|
567
|
+
FROM node:20-alpine
|
|
568
|
+
WORKDIR /app
|
|
569
|
+
COPY package*.json ./
|
|
570
|
+
RUN npm ci --production
|
|
571
|
+
COPY dist ./dist
|
|
572
|
+
CMD ["node", "dist/index.js"]
|
|
573
|
+
```
|
|
137
574
|
|
|
138
575
|
---
|
|
139
576
|
|
|
140
|
-
## 8.
|
|
577
|
+
## 8. Testing
|
|
578
|
+
|
|
579
|
+
### Unit Test Example
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
import { describe, it, expect } from "vitest";
|
|
583
|
+
import { greetTool } from "./tools/greet";
|
|
584
|
+
|
|
585
|
+
describe("greet tool", () => {
|
|
586
|
+
it("should greet user by name", async () => {
|
|
587
|
+
const result = await greetTool({ name: "Alice" });
|
|
588
|
+
expect(result.content[0].text).toContain("Hello, Alice");
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it("should handle empty name", async () => {
|
|
592
|
+
const result = await greetTool({ name: "" });
|
|
593
|
+
expect(result.content[0].text).toContain("error");
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
```
|
|
141
597
|
|
|
142
|
-
###
|
|
598
|
+
### Integration Test
|
|
143
599
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
| env | Environment variables |
|
|
600
|
+
```bash
|
|
601
|
+
# Test with MCP Inspector
|
|
602
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
603
|
+
```
|
|
149
604
|
|
|
150
605
|
---
|
|
151
606
|
|
|
152
|
-
## 9.
|
|
607
|
+
## 9. Common Patterns
|
|
608
|
+
|
|
609
|
+
### Progress Reporting
|
|
610
|
+
|
|
611
|
+
```python
|
|
612
|
+
@mcp.tool()
|
|
613
|
+
async def long_running_task(items: list[str]) -> dict:
|
|
614
|
+
"""Process items with progress updates."""
|
|
615
|
+
results = []
|
|
616
|
+
|
|
617
|
+
for i, item in enumerate(items):
|
|
618
|
+
# Process item
|
|
619
|
+
result = await process_item(item)
|
|
620
|
+
results.append(result)
|
|
621
|
+
|
|
622
|
+
# Report progress via logging
|
|
623
|
+
progress = (i + 1) / len(items) * 100
|
|
624
|
+
logging.info(f"Progress: {progress:.0f}%")
|
|
625
|
+
|
|
626
|
+
return {"results": results}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Caching
|
|
153
630
|
|
|
154
|
-
|
|
631
|
+
```python
|
|
632
|
+
from functools import lru_cache
|
|
155
633
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
634
|
+
@lru_cache(maxsize=100)
|
|
635
|
+
def expensive_lookup(key: str) -> dict:
|
|
636
|
+
"""Cached database lookup."""
|
|
637
|
+
return db.query(key)
|
|
638
|
+
|
|
639
|
+
@mcp.tool()
|
|
640
|
+
def get_user(user_id: str) -> dict:
|
|
641
|
+
"""Get user with caching."""
|
|
642
|
+
return expensive_lookup(user_id)
|
|
643
|
+
```
|
|
161
644
|
|
|
162
645
|
---
|
|
163
646
|
|
|
164
|
-
## 10.
|
|
647
|
+
## 10. Checklist
|
|
648
|
+
|
|
649
|
+
### Before Publishing
|
|
165
650
|
|
|
166
651
|
- [ ] Clear, action-oriented tool names
|
|
167
652
|
- [ ] Complete input schemas with descriptions
|
|
@@ -169,8 +654,9 @@ my-mcp-server/
|
|
|
169
654
|
- [ ] Error handling for all cases
|
|
170
655
|
- [ ] Input validation
|
|
171
656
|
- [ ] Environment-based configuration
|
|
172
|
-
- [ ]
|
|
657
|
+
- [ ] README with installation instructions
|
|
658
|
+
- [ ] Tests for critical paths
|
|
173
659
|
|
|
174
660
|
---
|
|
175
661
|
|
|
176
|
-
> **Remember:** MCP tools should be simple, focused, and well-documented. The AI relies on descriptions to use them correctly.
|
|
662
|
+
> **Remember:** MCP tools should be simple, focused, and well-documented. The AI relies on descriptions to use them correctly. Write tool descriptions as if explaining to a junior developer.
|