@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
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
# Python MCP Server Template (FastMCP)
|
|
2
|
+
|
|
3
|
+
> Copy this template to quickly start a Python MCP server.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
my-mcp-server/
|
|
9
|
+
├── src/
|
|
10
|
+
│ ├── __init__.py
|
|
11
|
+
│ ├── server.py # Entry point
|
|
12
|
+
│ ├── tools/ # Tool implementations
|
|
13
|
+
│ │ ├── __init__.py
|
|
14
|
+
│ │ └── example.py
|
|
15
|
+
│ ├── resources/ # Resource handlers
|
|
16
|
+
│ │ └── __init__.py
|
|
17
|
+
│ └── utils/ # Utilities
|
|
18
|
+
│ └── __init__.py
|
|
19
|
+
├── tests/
|
|
20
|
+
│ └── test_tools.py
|
|
21
|
+
├── pyproject.toml
|
|
22
|
+
├── requirements.txt
|
|
23
|
+
└── README.md
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Files
|
|
27
|
+
|
|
28
|
+
### pyproject.toml
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
[project]
|
|
32
|
+
name = "my-mcp-server"
|
|
33
|
+
version = "1.0.0"
|
|
34
|
+
description = "My MCP Server"
|
|
35
|
+
requires-python = ">=3.10"
|
|
36
|
+
dependencies = [
|
|
37
|
+
"fastmcp>=0.3.0",
|
|
38
|
+
"httpx>=0.27.0",
|
|
39
|
+
"pydantic>=2.0.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=8.0.0",
|
|
45
|
+
"pytest-asyncio>=0.23.0",
|
|
46
|
+
"ruff>=0.4.0",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.scripts]
|
|
50
|
+
my-mcp-server = "src.server:main"
|
|
51
|
+
|
|
52
|
+
[build-system]
|
|
53
|
+
requires = ["hatchling"]
|
|
54
|
+
build-backend = "hatchling.build"
|
|
55
|
+
|
|
56
|
+
[tool.ruff]
|
|
57
|
+
line-length = 100
|
|
58
|
+
target-version = "py310"
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
asyncio_mode = "auto"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### requirements.txt
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
fastmcp>=0.3.0
|
|
68
|
+
httpx>=0.27.0
|
|
69
|
+
pydantic>=2.0.0
|
|
70
|
+
python-dotenv>=1.0.0
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### src/server.py
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
#!/usr/bin/env python3
|
|
77
|
+
"""
|
|
78
|
+
My MCP Server - Main entry point
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
import os
|
|
82
|
+
import logging
|
|
83
|
+
from fastmcp import FastMCP
|
|
84
|
+
|
|
85
|
+
# Import tools and resources
|
|
86
|
+
from .tools.example import register_example_tools
|
|
87
|
+
from .resources import register_resources
|
|
88
|
+
|
|
89
|
+
# Configuration
|
|
90
|
+
SERVER_NAME = "my-mcp-server"
|
|
91
|
+
SERVER_VERSION = "1.0.0"
|
|
92
|
+
|
|
93
|
+
# Setup logging
|
|
94
|
+
logging.basicConfig(
|
|
95
|
+
level=logging.INFO,
|
|
96
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
97
|
+
)
|
|
98
|
+
logger = logging.getLogger(SERVER_NAME)
|
|
99
|
+
|
|
100
|
+
# Create server instance
|
|
101
|
+
mcp = FastMCP(
|
|
102
|
+
SERVER_NAME,
|
|
103
|
+
version=SERVER_VERSION,
|
|
104
|
+
description="My awesome MCP server"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Register tools and resources
|
|
108
|
+
register_example_tools(mcp)
|
|
109
|
+
register_resources(mcp)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main():
|
|
113
|
+
"""Entry point for the server."""
|
|
114
|
+
logger.info(f"Starting {SERVER_NAME} v{SERVER_VERSION}")
|
|
115
|
+
mcp.run()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
main()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### src/tools/__init__.py
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
"""Tool exports."""
|
|
126
|
+
|
|
127
|
+
from .example import register_example_tools
|
|
128
|
+
|
|
129
|
+
__all__ = ["register_example_tools"]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### src/tools/example.py
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
"""Example tools for the MCP server."""
|
|
136
|
+
|
|
137
|
+
import json
|
|
138
|
+
from typing import Optional
|
|
139
|
+
from datetime import datetime
|
|
140
|
+
from fastmcp import FastMCP
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def register_example_tools(mcp: FastMCP):
|
|
144
|
+
"""Register example tools with the server."""
|
|
145
|
+
|
|
146
|
+
@mcp.tool()
|
|
147
|
+
def greet(
|
|
148
|
+
name: str,
|
|
149
|
+
language: str = "en"
|
|
150
|
+
) -> str:
|
|
151
|
+
"""Greet a user by name.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
name: The name of the user to greet
|
|
155
|
+
language: Language for greeting (en, es, fr)
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
A greeting message
|
|
159
|
+
"""
|
|
160
|
+
greetings = {
|
|
161
|
+
"en": f"Hello, {name}!",
|
|
162
|
+
"es": f"¡Hola, {name}!",
|
|
163
|
+
"fr": f"Bonjour, {name}!",
|
|
164
|
+
}
|
|
165
|
+
return greetings.get(language, greetings["en"])
|
|
166
|
+
|
|
167
|
+
@mcp.tool()
|
|
168
|
+
def calculate(
|
|
169
|
+
operation: str,
|
|
170
|
+
a: float,
|
|
171
|
+
b: float
|
|
172
|
+
) -> dict:
|
|
173
|
+
"""Perform a mathematical calculation.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
operation: One of 'add', 'subtract', 'multiply', 'divide'
|
|
177
|
+
a: First number
|
|
178
|
+
b: Second number
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Result of the calculation
|
|
182
|
+
"""
|
|
183
|
+
operations = {
|
|
184
|
+
"add": lambda: a + b,
|
|
185
|
+
"subtract": lambda: a - b,
|
|
186
|
+
"multiply": lambda: a * b,
|
|
187
|
+
"divide": lambda: a / b if b != 0 else None,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if operation not in operations:
|
|
191
|
+
return {"error": f"Unknown operation: {operation}"}
|
|
192
|
+
|
|
193
|
+
result = operations[operation]()
|
|
194
|
+
|
|
195
|
+
if result is None:
|
|
196
|
+
return {"error": "Division by zero"}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
"operation": operation,
|
|
200
|
+
"a": a,
|
|
201
|
+
"b": b,
|
|
202
|
+
"result": result
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@mcp.tool()
|
|
206
|
+
def get_timestamp() -> dict:
|
|
207
|
+
"""Get current timestamp information.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Current date/time in various formats
|
|
211
|
+
"""
|
|
212
|
+
now = datetime.now()
|
|
213
|
+
return {
|
|
214
|
+
"iso": now.isoformat(),
|
|
215
|
+
"timestamp": int(now.timestamp()),
|
|
216
|
+
"formatted": now.strftime("%Y-%m-%d %H:%M:%S"),
|
|
217
|
+
"date": now.strftime("%Y-%m-%d"),
|
|
218
|
+
"time": now.strftime("%H:%M:%S"),
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@mcp.tool()
|
|
222
|
+
def json_format(
|
|
223
|
+
data: str,
|
|
224
|
+
indent: int = 2
|
|
225
|
+
) -> str:
|
|
226
|
+
"""Format JSON string with pretty printing.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
data: JSON string to format
|
|
230
|
+
indent: Number of spaces for indentation
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Formatted JSON string
|
|
234
|
+
"""
|
|
235
|
+
try:
|
|
236
|
+
parsed = json.loads(data)
|
|
237
|
+
return json.dumps(parsed, indent=indent, ensure_ascii=False)
|
|
238
|
+
except json.JSONDecodeError as e:
|
|
239
|
+
return f"Error parsing JSON: {e}"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### src/resources/__init__.py
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
"""Resource handlers for the MCP server."""
|
|
246
|
+
|
|
247
|
+
import json
|
|
248
|
+
import os
|
|
249
|
+
from datetime import datetime
|
|
250
|
+
from fastmcp import FastMCP
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def register_resources(mcp: FastMCP):
|
|
254
|
+
"""Register resources with the server."""
|
|
255
|
+
|
|
256
|
+
@mcp.resource("status://server")
|
|
257
|
+
def get_status() -> str:
|
|
258
|
+
"""Get server status information."""
|
|
259
|
+
return json.dumps({
|
|
260
|
+
"status": "running",
|
|
261
|
+
"timestamp": datetime.now().isoformat(),
|
|
262
|
+
"pid": os.getpid(),
|
|
263
|
+
"python_version": os.sys.version,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
@mcp.resource("config://settings")
|
|
267
|
+
def get_config() -> str:
|
|
268
|
+
"""Get server configuration."""
|
|
269
|
+
return json.dumps({
|
|
270
|
+
"name": "my-mcp-server",
|
|
271
|
+
"version": "1.0.0",
|
|
272
|
+
"features": ["tools", "resources", "prompts"],
|
|
273
|
+
"environment": os.environ.get("ENVIRONMENT", "development"),
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
@mcp.resource("env://variables")
|
|
277
|
+
def get_safe_env() -> str:
|
|
278
|
+
"""Get safe environment variables (non-sensitive)."""
|
|
279
|
+
safe_keys = ["PATH", "HOME", "USER", "SHELL", "LANG"]
|
|
280
|
+
safe_env = {
|
|
281
|
+
k: os.environ.get(k, "")
|
|
282
|
+
for k in safe_keys
|
|
283
|
+
if k in os.environ
|
|
284
|
+
}
|
|
285
|
+
return json.dumps(safe_env)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### src/utils/__init__.py
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
"""Utility functions for the MCP server."""
|
|
292
|
+
|
|
293
|
+
import os
|
|
294
|
+
import json
|
|
295
|
+
import hashlib
|
|
296
|
+
from typing import Any, Optional, TypeVar
|
|
297
|
+
from datetime import datetime
|
|
298
|
+
import logging
|
|
299
|
+
|
|
300
|
+
logger = logging.getLogger(__name__)
|
|
301
|
+
|
|
302
|
+
T = TypeVar("T")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def require_env(name: str) -> str:
|
|
306
|
+
"""Get required environment variable or raise error."""
|
|
307
|
+
value = os.environ.get(name)
|
|
308
|
+
if not value:
|
|
309
|
+
raise ValueError(f"Missing required environment variable: {name}")
|
|
310
|
+
return value
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def get_env(name: str, default: str = "") -> str:
|
|
314
|
+
"""Get environment variable with default."""
|
|
315
|
+
return os.environ.get(name, default)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def safe_json_parse(data: str, default: T = None) -> T:
|
|
319
|
+
"""Safely parse JSON with fallback."""
|
|
320
|
+
try:
|
|
321
|
+
return json.loads(data)
|
|
322
|
+
except json.JSONDecodeError:
|
|
323
|
+
return default
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def generate_id() -> str:
|
|
327
|
+
"""Generate a unique ID."""
|
|
328
|
+
timestamp = datetime.now().isoformat()
|
|
329
|
+
return hashlib.sha256(timestamp.encode()).hexdigest()[:12]
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def truncate(text: str, max_length: int = 100) -> str:
|
|
333
|
+
"""Truncate text with ellipsis."""
|
|
334
|
+
if len(text) <= max_length:
|
|
335
|
+
return text
|
|
336
|
+
return text[:max_length - 3] + "..."
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def log_tool_call(tool_name: str, args: dict, result: Any) -> None:
|
|
340
|
+
"""Log tool calls for debugging."""
|
|
341
|
+
logger.info(f"Tool: {tool_name}, Args: {args}, Result type: {type(result).__name__}")
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### tests/test_tools.py
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
"""Tests for MCP tools."""
|
|
348
|
+
|
|
349
|
+
import pytest
|
|
350
|
+
from src.tools.example import register_example_tools
|
|
351
|
+
from fastmcp import FastMCP
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@pytest.fixture
|
|
355
|
+
def mcp():
|
|
356
|
+
"""Create a test MCP server."""
|
|
357
|
+
server = FastMCP("test-server")
|
|
358
|
+
register_example_tools(server)
|
|
359
|
+
return server
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def test_greet_english():
|
|
363
|
+
"""Test greeting in English."""
|
|
364
|
+
from src.tools.example import register_example_tools
|
|
365
|
+
|
|
366
|
+
mcp = FastMCP("test")
|
|
367
|
+
register_example_tools(mcp)
|
|
368
|
+
|
|
369
|
+
# Tools are registered, you would call them through the server
|
|
370
|
+
# This is a simplified example
|
|
371
|
+
assert True # Replace with actual test
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_calculate_add():
|
|
375
|
+
"""Test addition calculation."""
|
|
376
|
+
# Import the functions directly for unit testing
|
|
377
|
+
result = {"operation": "add", "a": 2, "b": 3, "result": 5}
|
|
378
|
+
assert result["result"] == 5
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def test_calculate_divide_by_zero():
|
|
382
|
+
"""Test division by zero handling."""
|
|
383
|
+
result = {"error": "Division by zero"}
|
|
384
|
+
assert "error" in result
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Running the Server
|
|
388
|
+
|
|
389
|
+
### Development
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
# Install dependencies
|
|
393
|
+
pip install -r requirements.txt
|
|
394
|
+
|
|
395
|
+
# Or with uv (faster)
|
|
396
|
+
uv pip install -r requirements.txt
|
|
397
|
+
|
|
398
|
+
# Run in development mode
|
|
399
|
+
fastmcp dev src/server.py
|
|
400
|
+
|
|
401
|
+
# Run with hot reload
|
|
402
|
+
fastmcp dev src/server.py --reload
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Production
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# Install as package
|
|
409
|
+
pip install .
|
|
410
|
+
|
|
411
|
+
# Run
|
|
412
|
+
my-mcp-server
|
|
413
|
+
|
|
414
|
+
# Or directly
|
|
415
|
+
python -m src.server
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Claude Desktop Configuration
|
|
419
|
+
|
|
420
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
421
|
+
|
|
422
|
+
```json
|
|
423
|
+
{
|
|
424
|
+
"mcpServers": {
|
|
425
|
+
"my-server": {
|
|
426
|
+
"command": "python",
|
|
427
|
+
"args": ["-m", "src.server"],
|
|
428
|
+
"cwd": "/path/to/my-mcp-server",
|
|
429
|
+
"env": {
|
|
430
|
+
"PYTHONPATH": "/path/to/my-mcp-server",
|
|
431
|
+
"MY_API_KEY": "your-api-key"
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Or using fastmcp:
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"mcpServers": {
|
|
443
|
+
"my-server": {
|
|
444
|
+
"command": "fastmcp",
|
|
445
|
+
"args": ["run", "/path/to/my-mcp-server/src/server.py"]
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## FastMCP Installation Commands
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
# Install directly to Claude Desktop
|
|
455
|
+
fastmcp install src/server.py --name "My Server"
|
|
456
|
+
|
|
457
|
+
# Install with environment variables
|
|
458
|
+
fastmcp install src/server.py --name "My Server" \
|
|
459
|
+
--env MY_API_KEY=secret \
|
|
460
|
+
--env DATABASE_URL=postgres://...
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Testing
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
# Run tests
|
|
467
|
+
pytest
|
|
468
|
+
|
|
469
|
+
# Run with coverage
|
|
470
|
+
pytest --cov=src
|
|
471
|
+
|
|
472
|
+
# Test with MCP Inspector
|
|
473
|
+
fastmcp dev src/server.py
|
|
474
|
+
# Then open the inspector URL in browser
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Common Patterns
|
|
478
|
+
|
|
479
|
+
### Async Tool
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
@mcp.tool()
|
|
483
|
+
async def fetch_data(url: str) -> dict:
|
|
484
|
+
"""Fetch data from URL."""
|
|
485
|
+
import httpx
|
|
486
|
+
async with httpx.AsyncClient() as client:
|
|
487
|
+
response = await client.get(url)
|
|
488
|
+
return response.json()
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Tool with Validation
|
|
492
|
+
|
|
493
|
+
```python
|
|
494
|
+
from pydantic import BaseModel, validator
|
|
495
|
+
|
|
496
|
+
class UserInput(BaseModel):
|
|
497
|
+
name: str
|
|
498
|
+
age: int
|
|
499
|
+
|
|
500
|
+
@validator("age")
|
|
501
|
+
def validate_age(cls, v):
|
|
502
|
+
if v < 0 or v > 150:
|
|
503
|
+
raise ValueError("Age must be between 0 and 150")
|
|
504
|
+
return v
|
|
505
|
+
|
|
506
|
+
@mcp.tool()
|
|
507
|
+
def create_user(user: UserInput) -> dict:
|
|
508
|
+
"""Create a new user with validation."""
|
|
509
|
+
return {"id": generate_id(), **user.dict()}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Context-aware Tool
|
|
513
|
+
|
|
514
|
+
```python
|
|
515
|
+
@mcp.tool()
|
|
516
|
+
def get_context_info(ctx) -> dict:
|
|
517
|
+
"""Get information about the current context."""
|
|
518
|
+
return {
|
|
519
|
+
"client_id": ctx.client_id if hasattr(ctx, "client_id") else "unknown",
|
|
520
|
+
"request_id": ctx.request_id if hasattr(ctx, "request_id") else "none",
|
|
521
|
+
}
|
|
522
|
+
```
|