omgkit 2.2.0 → 2.3.0
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.
- package/package.json +1 -1
- package/plugin/skills/databases/mongodb/SKILL.md +60 -776
- package/plugin/skills/databases/prisma/SKILL.md +53 -744
- package/plugin/skills/databases/redis/SKILL.md +53 -860
- package/plugin/skills/devops/aws/SKILL.md +68 -672
- package/plugin/skills/devops/github-actions/SKILL.md +54 -657
- package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
- package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
- package/plugin/skills/frameworks/django/SKILL.md +87 -853
- package/plugin/skills/frameworks/express/SKILL.md +95 -1301
- package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
- package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
- package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
- package/plugin/skills/frameworks/react/SKILL.md +94 -962
- package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
- package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
- package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
- package/plugin/skills/frontend/responsive/SKILL.md +76 -799
- package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
- package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
- package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
- package/plugin/skills/languages/javascript/SKILL.md +106 -849
- package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
- package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
- package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
- package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
- package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
- package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
- package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
- package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
- package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
- package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
- package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
- package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
- package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
- package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
- package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
- package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
- package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
- package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
- package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
- package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
- package/plugin/skills/security/better-auth/SKILL.md +46 -1034
- package/plugin/skills/security/oauth/SKILL.md +80 -934
- package/plugin/skills/security/owasp/SKILL.md +78 -862
- package/plugin/skills/testing/playwright/SKILL.md +77 -700
- package/plugin/skills/testing/pytest/SKILL.md +73 -811
- package/plugin/skills/testing/vitest/SKILL.md +60 -920
- package/plugin/skills/tools/document-processing/SKILL.md +111 -838
- package/plugin/skills/tools/image-processing/SKILL.md +126 -659
- package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
- package/plugin/skills/tools/media-processing/SKILL.md +118 -735
- package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
- package/plugin/skills/SKILL_STANDARDS.md +0 -743
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
2
|
+
name: Developing MCP Servers
|
|
3
|
+
description: Creates Model Context Protocol servers with tools, resources, and prompts for AI assistant integration. Use when extending Claude with custom capabilities, building AI tool integrations, or exposing APIs to AI assistants.
|
|
4
4
|
category: tools
|
|
5
5
|
triggers:
|
|
6
6
|
- mcp server
|
|
@@ -12,721 +12,98 @@ triggers:
|
|
|
12
12
|
- tool creation
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
# MCP
|
|
15
|
+
# Developing MCP Servers
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## Purpose
|
|
20
|
-
|
|
21
|
-
MCP enables AI assistants to interact with external systems securely and efficiently:
|
|
22
|
-
|
|
23
|
-
- Create custom tools that AI can use during conversations
|
|
24
|
-
- Expose data resources for AI to read and analyze
|
|
25
|
-
- Define prompt templates for consistent AI interactions
|
|
26
|
-
- Build integrations with databases, APIs, and services
|
|
27
|
-
- Manage server lifecycle and error handling
|
|
28
|
-
|
|
29
|
-
## Features
|
|
30
|
-
|
|
31
|
-
### 1. FastMCP Server (Python)
|
|
17
|
+
## Quick Start
|
|
32
18
|
|
|
33
19
|
```python
|
|
34
|
-
# Basic MCP server with FastMCP
|
|
35
20
|
from fastmcp import FastMCP
|
|
36
21
|
|
|
37
|
-
# Initialize server
|
|
38
22
|
mcp = FastMCP("my-service")
|
|
39
23
|
|
|
40
|
-
# Define a simple tool
|
|
41
24
|
@mcp.tool()
|
|
42
25
|
def get_weather(city: str) -> str:
|
|
43
26
|
"""Get current weather for a city.
|
|
44
27
|
|
|
45
28
|
Args:
|
|
46
29
|
city: Name of the city to get weather for
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
Weather information as a formatted string
|
|
50
|
-
"""
|
|
51
|
-
# Fetch from weather API
|
|
52
|
-
response = requests.get(f"https://api.weather.com/{city}")
|
|
53
|
-
data = response.json()
|
|
54
|
-
|
|
55
|
-
return f"""
|
|
56
|
-
Weather in {city}:
|
|
57
|
-
Temperature: {data['temp']}°F
|
|
58
|
-
Conditions: {data['conditions']}
|
|
59
|
-
Humidity: {data['humidity']}%
|
|
60
30
|
"""
|
|
31
|
+
return f"Weather in {city}: 72F, Sunny"
|
|
61
32
|
|
|
62
|
-
# Tool with complex parameters
|
|
63
|
-
@mcp.tool()
|
|
64
|
-
def search_database(
|
|
65
|
-
query: str,
|
|
66
|
-
table: str = "users",
|
|
67
|
-
limit: int = 10,
|
|
68
|
-
include_deleted: bool = False
|
|
69
|
-
) -> list[dict]:
|
|
70
|
-
"""Search the database with flexible parameters.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
query: Search query string
|
|
74
|
-
table: Table to search (default: users)
|
|
75
|
-
limit: Maximum results to return (default: 10)
|
|
76
|
-
include_deleted: Include soft-deleted records
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
List of matching records
|
|
80
|
-
"""
|
|
81
|
-
sql = f"SELECT * FROM {table} WHERE content LIKE %s"
|
|
82
|
-
if not include_deleted:
|
|
83
|
-
sql += " AND deleted_at IS NULL"
|
|
84
|
-
sql += f" LIMIT {limit}"
|
|
85
|
-
|
|
86
|
-
return db.execute(sql, [f"%{query}%"]).fetchall()
|
|
87
|
-
|
|
88
|
-
# Resource exposure
|
|
89
33
|
@mcp.resource("config://settings")
|
|
90
34
|
def get_settings() -> str:
|
|
91
35
|
"""Expose application settings as a resource."""
|
|
92
|
-
return json.dumps(
|
|
36
|
+
return json.dumps({"theme": "dark", "language": "en"})
|
|
93
37
|
|
|
94
|
-
# Dynamic resource with URI template
|
|
95
|
-
@mcp.resource("user://{user_id}/profile")
|
|
96
|
-
def get_user_profile(user_id: str) -> str:
|
|
97
|
-
"""Get user profile data.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
user_id: The user's unique identifier
|
|
101
|
-
"""
|
|
102
|
-
user = db.get_user(user_id)
|
|
103
|
-
return json.dumps(user.to_dict())
|
|
104
|
-
|
|
105
|
-
# Prompt template
|
|
106
38
|
@mcp.prompt()
|
|
107
39
|
def analyze_code(code: str, language: str = "python") -> str:
|
|
108
|
-
"""Generate a prompt for code analysis.
|
|
109
|
-
|
|
110
|
-
Args:
|
|
111
|
-
code: The code to analyze
|
|
112
|
-
language: Programming language
|
|
113
|
-
"""
|
|
114
|
-
return f"""
|
|
115
|
-
Please analyze the following {language} code:
|
|
116
|
-
|
|
117
|
-
```{language}
|
|
118
|
-
{code}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Provide:
|
|
122
|
-
1. A brief summary of what the code does
|
|
123
|
-
2. Potential bugs or issues
|
|
124
|
-
3. Performance considerations
|
|
125
|
-
4. Security concerns
|
|
126
|
-
5. Suggested improvements
|
|
127
|
-
"""
|
|
40
|
+
"""Generate a prompt for code analysis."""
|
|
41
|
+
return f"Analyze this {language} code:\n```{language}\n{code}\n```"
|
|
128
42
|
|
|
129
|
-
# Run server
|
|
130
43
|
if __name__ == "__main__":
|
|
131
44
|
mcp.run()
|
|
132
45
|
```
|
|
133
46
|
|
|
134
|
-
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
| Feature | Description | Guide |
|
|
50
|
+
|---------|-------------|-------|
|
|
51
|
+
| Tool Definition | Create callable tools with typed parameters | Use @mcp.tool() decorator with docstrings |
|
|
52
|
+
| Resource Exposure | Expose data resources for AI to read | Use @mcp.resource() with URI patterns |
|
|
53
|
+
| Prompt Templates | Define reusable prompt templates | Use @mcp.prompt() for consistent prompts |
|
|
54
|
+
| Dynamic Resources | Create parameterized resource URIs | Use URI templates like "user://{id}/profile" |
|
|
55
|
+
| Progress Reporting | Report progress for long operations | Use ctx.report_progress() in async tools |
|
|
56
|
+
| Streaming Results | Stream results for large outputs | Use AsyncGenerator return type |
|
|
57
|
+
| Lifecycle Hooks | Manage server startup and shutdown | Use lifespan context manager |
|
|
58
|
+
| Middleware | Add logging, rate limiting, auth | Implement middleware functions |
|
|
59
|
+
| Error Handling | Return structured error responses | Use try/except with error codes |
|
|
60
|
+
| Testing | Test tools and resources in isolation | Use MCPTestClient for unit tests |
|
|
61
|
+
|
|
62
|
+
## Common Patterns
|
|
63
|
+
|
|
64
|
+
### TypeScript MCP Server
|
|
135
65
|
|
|
136
66
|
```typescript
|
|
137
67
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
138
68
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
139
|
-
|
|
140
|
-
CallToolRequestSchema,
|
|
141
|
-
ListResourcesRequestSchema,
|
|
142
|
-
ListToolsRequestSchema,
|
|
143
|
-
ReadResourceRequestSchema,
|
|
144
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
145
|
-
import { z } from "zod";
|
|
146
|
-
|
|
147
|
-
// Server configuration
|
|
69
|
+
|
|
148
70
|
const server = new Server(
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
version: "1.0.0",
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
capabilities: {
|
|
155
|
-
tools: {},
|
|
156
|
-
resources: {},
|
|
157
|
-
prompts: {},
|
|
158
|
-
},
|
|
159
|
-
}
|
|
71
|
+
{ name: "my-server", version: "1.0.0" },
|
|
72
|
+
{ capabilities: { tools: {}, resources: {} } }
|
|
160
73
|
);
|
|
161
74
|
|
|
162
|
-
// Tool definitions with Zod schemas
|
|
163
|
-
const FileSearchSchema = z.object({
|
|
164
|
-
query: z.string().describe("Search query"),
|
|
165
|
-
directory: z.string().optional().describe("Directory to search in"),
|
|
166
|
-
fileTypes: z.array(z.string()).optional().describe("File extensions to include"),
|
|
167
|
-
maxResults: z.number().optional().default(20).describe("Maximum results"),
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
const DatabaseQuerySchema = z.object({
|
|
171
|
-
sql: z.string().describe("SQL query to execute"),
|
|
172
|
-
params: z.array(z.unknown()).optional().describe("Query parameters"),
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// Register tools
|
|
176
75
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
177
|
-
tools: [
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
query: { type: "string", description: "Search query" },
|
|
185
|
-
directory: { type: "string", description: "Directory to search" },
|
|
186
|
-
fileTypes: {
|
|
187
|
-
type: "array",
|
|
188
|
-
items: { type: "string" },
|
|
189
|
-
description: "File extensions",
|
|
190
|
-
},
|
|
191
|
-
maxResults: {
|
|
192
|
-
type: "number",
|
|
193
|
-
description: "Max results",
|
|
194
|
-
default: 20,
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
required: ["query"],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: "database_query",
|
|
202
|
-
description: "Execute a read-only database query",
|
|
203
|
-
inputSchema: {
|
|
204
|
-
type: "object",
|
|
205
|
-
properties: {
|
|
206
|
-
sql: { type: "string", description: "SQL query" },
|
|
207
|
-
params: { type: "array", description: "Query parameters" },
|
|
208
|
-
},
|
|
209
|
-
required: ["sql"],
|
|
210
|
-
},
|
|
76
|
+
tools: [{
|
|
77
|
+
name: "search",
|
|
78
|
+
description: "Search the database",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: { query: { type: "string" } },
|
|
82
|
+
required: ["query"],
|
|
211
83
|
},
|
|
212
|
-
],
|
|
84
|
+
}],
|
|
213
85
|
}));
|
|
214
86
|
|
|
215
|
-
// Handle tool calls
|
|
216
87
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
217
88
|
const { name, arguments: args } = request.params;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
case "file_search": {
|
|
222
|
-
const params = FileSearchSchema.parse(args);
|
|
223
|
-
const results = await searchFiles(params);
|
|
224
|
-
return {
|
|
225
|
-
content: [
|
|
226
|
-
{
|
|
227
|
-
type: "text",
|
|
228
|
-
text: JSON.stringify(results, null, 2),
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
case "database_query": {
|
|
235
|
-
const params = DatabaseQuerySchema.parse(args);
|
|
236
|
-
// Validate read-only
|
|
237
|
-
if (!isReadOnlyQuery(params.sql)) {
|
|
238
|
-
throw new Error("Only SELECT queries are allowed");
|
|
239
|
-
}
|
|
240
|
-
const results = await executeQuery(params.sql, params.params);
|
|
241
|
-
return {
|
|
242
|
-
content: [
|
|
243
|
-
{
|
|
244
|
-
type: "text",
|
|
245
|
-
text: JSON.stringify(results, null, 2),
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
default:
|
|
252
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
253
|
-
}
|
|
254
|
-
} catch (error) {
|
|
255
|
-
return {
|
|
256
|
-
content: [
|
|
257
|
-
{
|
|
258
|
-
type: "text",
|
|
259
|
-
text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
isError: true,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Resource handlers
|
|
268
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
269
|
-
resources: [
|
|
270
|
-
{
|
|
271
|
-
uri: "config://app",
|
|
272
|
-
name: "Application Configuration",
|
|
273
|
-
description: "Current application configuration",
|
|
274
|
-
mimeType: "application/json",
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
uri: "schema://database",
|
|
278
|
-
name: "Database Schema",
|
|
279
|
-
description: "Database table definitions",
|
|
280
|
-
mimeType: "application/json",
|
|
281
|
-
},
|
|
282
|
-
],
|
|
283
|
-
}));
|
|
284
|
-
|
|
285
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
286
|
-
const { uri } = request.params;
|
|
287
|
-
|
|
288
|
-
if (uri === "config://app") {
|
|
289
|
-
return {
|
|
290
|
-
contents: [
|
|
291
|
-
{
|
|
292
|
-
uri,
|
|
293
|
-
mimeType: "application/json",
|
|
294
|
-
text: JSON.stringify(getAppConfig(), null, 2),
|
|
295
|
-
},
|
|
296
|
-
],
|
|
297
|
-
};
|
|
89
|
+
if (name === "search") {
|
|
90
|
+
const results = await searchDatabase(args.query);
|
|
91
|
+
return { content: [{ type: "text", text: JSON.stringify(results) }] };
|
|
298
92
|
}
|
|
299
|
-
|
|
300
|
-
if (uri === "schema://database") {
|
|
301
|
-
return {
|
|
302
|
-
contents: [
|
|
303
|
-
{
|
|
304
|
-
uri,
|
|
305
|
-
mimeType: "application/json",
|
|
306
|
-
text: JSON.stringify(getDatabaseSchema(), null, 2),
|
|
307
|
-
},
|
|
308
|
-
],
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
throw new Error(`Unknown resource: ${uri}`);
|
|
93
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
313
94
|
});
|
|
314
95
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const transport = new StdioServerTransport();
|
|
318
|
-
await server.connect(transport);
|
|
319
|
-
console.error("MCP server running on stdio");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
main().catch(console.error);
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### 3. Advanced Tool Patterns
|
|
326
|
-
|
|
327
|
-
```python
|
|
328
|
-
from fastmcp import FastMCP, Context
|
|
329
|
-
from typing import AsyncGenerator
|
|
330
|
-
import asyncio
|
|
331
|
-
|
|
332
|
-
mcp = FastMCP("advanced-tools")
|
|
333
|
-
|
|
334
|
-
# Context-aware tool
|
|
335
|
-
@mcp.tool()
|
|
336
|
-
async def analyze_file(ctx: Context, file_path: str) -> str:
|
|
337
|
-
"""Analyze a file using context for progress reporting.
|
|
338
|
-
|
|
339
|
-
Args:
|
|
340
|
-
ctx: MCP context for progress updates
|
|
341
|
-
file_path: Path to the file to analyze
|
|
342
|
-
"""
|
|
343
|
-
# Report progress
|
|
344
|
-
await ctx.report_progress(0, 100, "Starting analysis...")
|
|
345
|
-
|
|
346
|
-
content = await read_file(file_path)
|
|
347
|
-
await ctx.report_progress(25, 100, "File loaded")
|
|
348
|
-
|
|
349
|
-
# Perform analysis
|
|
350
|
-
syntax_check = await check_syntax(content)
|
|
351
|
-
await ctx.report_progress(50, 100, "Syntax checked")
|
|
352
|
-
|
|
353
|
-
security_scan = await scan_security(content)
|
|
354
|
-
await ctx.report_progress(75, 100, "Security scanned")
|
|
355
|
-
|
|
356
|
-
quality_metrics = await analyze_quality(content)
|
|
357
|
-
await ctx.report_progress(100, 100, "Analysis complete")
|
|
358
|
-
|
|
359
|
-
return json.dumps({
|
|
360
|
-
"syntax": syntax_check,
|
|
361
|
-
"security": security_scan,
|
|
362
|
-
"quality": quality_metrics,
|
|
363
|
-
}, indent=2)
|
|
364
|
-
|
|
365
|
-
# Streaming tool for long operations
|
|
366
|
-
@mcp.tool()
|
|
367
|
-
async def stream_logs(
|
|
368
|
-
log_file: str,
|
|
369
|
-
filter_pattern: str | None = None,
|
|
370
|
-
follow: bool = False
|
|
371
|
-
) -> AsyncGenerator[str, None]:
|
|
372
|
-
"""Stream log file content with optional filtering.
|
|
373
|
-
|
|
374
|
-
Args:
|
|
375
|
-
log_file: Path to log file
|
|
376
|
-
filter_pattern: Regex pattern to filter logs
|
|
377
|
-
follow: Whether to follow new entries (like tail -f)
|
|
378
|
-
|
|
379
|
-
Yields:
|
|
380
|
-
Filtered log lines
|
|
381
|
-
"""
|
|
382
|
-
pattern = re.compile(filter_pattern) if filter_pattern else None
|
|
383
|
-
|
|
384
|
-
async with aiofiles.open(log_file, 'r') as f:
|
|
385
|
-
# Read existing content
|
|
386
|
-
async for line in f:
|
|
387
|
-
if pattern is None or pattern.search(line):
|
|
388
|
-
yield line
|
|
389
|
-
|
|
390
|
-
# Follow new content if requested
|
|
391
|
-
if follow:
|
|
392
|
-
while True:
|
|
393
|
-
line = await f.readline()
|
|
394
|
-
if line:
|
|
395
|
-
if pattern is None or pattern.search(line):
|
|
396
|
-
yield line
|
|
397
|
-
else:
|
|
398
|
-
await asyncio.sleep(0.1)
|
|
399
|
-
|
|
400
|
-
# Tool with complex return type
|
|
401
|
-
@mcp.tool()
|
|
402
|
-
def query_with_pagination(
|
|
403
|
-
query: str,
|
|
404
|
-
page: int = 1,
|
|
405
|
-
page_size: int = 20
|
|
406
|
-
) -> dict:
|
|
407
|
-
"""Query data with pagination support.
|
|
408
|
-
|
|
409
|
-
Args:
|
|
410
|
-
query: Search query
|
|
411
|
-
page: Page number (1-indexed)
|
|
412
|
-
page_size: Items per page
|
|
413
|
-
|
|
414
|
-
Returns:
|
|
415
|
-
Paginated results with metadata
|
|
416
|
-
"""
|
|
417
|
-
offset = (page - 1) * page_size
|
|
418
|
-
results = db.search(query, limit=page_size, offset=offset)
|
|
419
|
-
total = db.count(query)
|
|
420
|
-
|
|
421
|
-
return {
|
|
422
|
-
"data": results,
|
|
423
|
-
"pagination": {
|
|
424
|
-
"page": page,
|
|
425
|
-
"page_size": page_size,
|
|
426
|
-
"total_items": total,
|
|
427
|
-
"total_pages": (total + page_size - 1) // page_size,
|
|
428
|
-
"has_next": page * page_size < total,
|
|
429
|
-
"has_prev": page > 1,
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
# Error handling wrapper
|
|
434
|
-
@mcp.tool()
|
|
435
|
-
def safe_operation(operation: str, params: dict) -> dict:
|
|
436
|
-
"""Execute operation with comprehensive error handling.
|
|
437
|
-
|
|
438
|
-
Args:
|
|
439
|
-
operation: Operation name to execute
|
|
440
|
-
params: Operation parameters
|
|
441
|
-
|
|
442
|
-
Returns:
|
|
443
|
-
Operation result or error details
|
|
444
|
-
"""
|
|
445
|
-
try:
|
|
446
|
-
result = execute_operation(operation, params)
|
|
447
|
-
return {
|
|
448
|
-
"success": True,
|
|
449
|
-
"data": result,
|
|
450
|
-
"timestamp": datetime.utcnow().isoformat(),
|
|
451
|
-
}
|
|
452
|
-
except ValidationError as e:
|
|
453
|
-
return {
|
|
454
|
-
"success": False,
|
|
455
|
-
"error": "validation_error",
|
|
456
|
-
"message": str(e),
|
|
457
|
-
"fields": e.errors(),
|
|
458
|
-
}
|
|
459
|
-
except PermissionError as e:
|
|
460
|
-
return {
|
|
461
|
-
"success": False,
|
|
462
|
-
"error": "permission_denied",
|
|
463
|
-
"message": str(e),
|
|
464
|
-
}
|
|
465
|
-
except Exception as e:
|
|
466
|
-
logger.exception("Operation failed")
|
|
467
|
-
return {
|
|
468
|
-
"success": False,
|
|
469
|
-
"error": "internal_error",
|
|
470
|
-
"message": "An unexpected error occurred",
|
|
471
|
-
"reference_id": generate_error_id(),
|
|
472
|
-
}
|
|
96
|
+
const transport = new StdioServerTransport();
|
|
97
|
+
await server.connect(transport);
|
|
473
98
|
```
|
|
474
99
|
|
|
475
|
-
###
|
|
100
|
+
### Database Integration Server
|
|
476
101
|
|
|
477
102
|
```python
|
|
478
103
|
from fastmcp import FastMCP
|
|
479
|
-
from typing import Optional
|
|
480
|
-
import yaml
|
|
481
|
-
|
|
482
|
-
mcp = FastMCP("resource-server")
|
|
483
|
-
|
|
484
|
-
# Static resource
|
|
485
|
-
@mcp.resource("config://database")
|
|
486
|
-
def database_config() -> str:
|
|
487
|
-
"""Database configuration resource."""
|
|
488
|
-
return yaml.dump({
|
|
489
|
-
"host": os.getenv("DB_HOST", "localhost"),
|
|
490
|
-
"port": int(os.getenv("DB_PORT", 5432)),
|
|
491
|
-
"database": os.getenv("DB_NAME", "app"),
|
|
492
|
-
"pool_size": 10,
|
|
493
|
-
})
|
|
494
|
-
|
|
495
|
-
# Dynamic resource with template
|
|
496
|
-
@mcp.resource("table://{schema}/{table}/schema")
|
|
497
|
-
def table_schema(schema: str, table: str) -> str:
|
|
498
|
-
"""Get schema definition for a specific table.
|
|
499
|
-
|
|
500
|
-
Args:
|
|
501
|
-
schema: Database schema name
|
|
502
|
-
table: Table name
|
|
503
|
-
"""
|
|
504
|
-
columns = db.get_table_schema(schema, table)
|
|
505
|
-
return json.dumps({
|
|
506
|
-
"schema": schema,
|
|
507
|
-
"table": table,
|
|
508
|
-
"columns": [
|
|
509
|
-
{
|
|
510
|
-
"name": col.name,
|
|
511
|
-
"type": col.type,
|
|
512
|
-
"nullable": col.nullable,
|
|
513
|
-
"default": col.default,
|
|
514
|
-
"primary_key": col.primary_key,
|
|
515
|
-
}
|
|
516
|
-
for col in columns
|
|
517
|
-
],
|
|
518
|
-
"indexes": db.get_indexes(schema, table),
|
|
519
|
-
"foreign_keys": db.get_foreign_keys(schema, table),
|
|
520
|
-
}, indent=2)
|
|
521
|
-
|
|
522
|
-
# Resource with MIME type
|
|
523
|
-
@mcp.resource("file://{path}", mime_type="text/plain")
|
|
524
|
-
def file_content(path: str) -> str:
|
|
525
|
-
"""Read file content as resource.
|
|
526
|
-
|
|
527
|
-
Args:
|
|
528
|
-
path: File path relative to workspace
|
|
529
|
-
"""
|
|
530
|
-
full_path = os.path.join(WORKSPACE_ROOT, path)
|
|
531
|
-
|
|
532
|
-
# Security: ensure path is within workspace
|
|
533
|
-
if not os.path.realpath(full_path).startswith(WORKSPACE_ROOT):
|
|
534
|
-
raise ValueError("Path outside workspace")
|
|
535
|
-
|
|
536
|
-
with open(full_path, 'r') as f:
|
|
537
|
-
return f.read()
|
|
538
|
-
|
|
539
|
-
# Binary resource
|
|
540
|
-
@mcp.resource("image://{image_id}", mime_type="image/png")
|
|
541
|
-
def get_image(image_id: str) -> bytes:
|
|
542
|
-
"""Get image data as binary resource.
|
|
543
|
-
|
|
544
|
-
Args:
|
|
545
|
-
image_id: Image identifier
|
|
546
|
-
"""
|
|
547
|
-
return storage.get_image(image_id)
|
|
548
|
-
|
|
549
|
-
# Aggregated resource
|
|
550
|
-
@mcp.resource("project://overview")
|
|
551
|
-
def project_overview() -> str:
|
|
552
|
-
"""Get complete project overview."""
|
|
553
|
-
return json.dumps({
|
|
554
|
-
"name": project.name,
|
|
555
|
-
"version": project.version,
|
|
556
|
-
"dependencies": project.get_dependencies(),
|
|
557
|
-
"scripts": project.get_scripts(),
|
|
558
|
-
"structure": project.get_directory_tree(),
|
|
559
|
-
"recent_changes": project.get_recent_commits(10),
|
|
560
|
-
}, indent=2)
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
### 5. Server Lifecycle and Configuration
|
|
564
|
-
|
|
565
|
-
```python
|
|
566
|
-
from fastmcp import FastMCP
|
|
567
|
-
from contextlib import asynccontextmanager
|
|
568
|
-
|
|
569
|
-
# Server with lifecycle hooks
|
|
570
|
-
@asynccontextmanager
|
|
571
|
-
async def lifespan(server: FastMCP):
|
|
572
|
-
"""Manage server lifecycle."""
|
|
573
|
-
# Startup
|
|
574
|
-
print("Starting MCP server...")
|
|
575
|
-
await database.connect()
|
|
576
|
-
await cache.connect()
|
|
577
|
-
|
|
578
|
-
yield # Server is running
|
|
579
|
-
|
|
580
|
-
# Shutdown
|
|
581
|
-
print("Shutting down MCP server...")
|
|
582
|
-
await database.disconnect()
|
|
583
|
-
await cache.disconnect()
|
|
584
|
-
|
|
585
|
-
mcp = FastMCP("lifecycle-server", lifespan=lifespan)
|
|
586
|
-
|
|
587
|
-
# Configuration management
|
|
588
|
-
class ServerConfig:
|
|
589
|
-
def __init__(self):
|
|
590
|
-
self.debug = os.getenv("DEBUG", "false").lower() == "true"
|
|
591
|
-
self.max_connections = int(os.getenv("MAX_CONNECTIONS", 100))
|
|
592
|
-
self.timeout = int(os.getenv("TIMEOUT", 30))
|
|
593
|
-
self.allowed_paths = os.getenv("ALLOWED_PATHS", ".").split(",")
|
|
594
|
-
|
|
595
|
-
config = ServerConfig()
|
|
596
|
-
|
|
597
|
-
# Middleware for logging
|
|
598
|
-
@mcp.middleware
|
|
599
|
-
async def logging_middleware(request, call_next):
|
|
600
|
-
"""Log all tool calls."""
|
|
601
|
-
start_time = time.time()
|
|
602
|
-
logger.info(f"Tool call: {request.method} - {request.params}")
|
|
603
|
-
|
|
604
|
-
response = await call_next(request)
|
|
605
|
-
|
|
606
|
-
duration = time.time() - start_time
|
|
607
|
-
logger.info(f"Completed in {duration:.2f}s")
|
|
608
|
-
|
|
609
|
-
return response
|
|
610
|
-
|
|
611
|
-
# Rate limiting middleware
|
|
612
|
-
@mcp.middleware
|
|
613
|
-
async def rate_limit_middleware(request, call_next):
|
|
614
|
-
"""Apply rate limiting."""
|
|
615
|
-
client_id = request.context.get("client_id", "default")
|
|
616
|
-
|
|
617
|
-
if not rate_limiter.allow(client_id):
|
|
618
|
-
raise RateLimitError("Too many requests")
|
|
619
|
-
|
|
620
|
-
return await call_next(request)
|
|
621
|
-
|
|
622
|
-
# Health check resource
|
|
623
|
-
@mcp.resource("health://status")
|
|
624
|
-
def health_status() -> str:
|
|
625
|
-
"""Server health status."""
|
|
626
|
-
return json.dumps({
|
|
627
|
-
"status": "healthy",
|
|
628
|
-
"uptime": get_uptime(),
|
|
629
|
-
"connections": {
|
|
630
|
-
"database": database.is_connected(),
|
|
631
|
-
"cache": cache.is_connected(),
|
|
632
|
-
},
|
|
633
|
-
"metrics": {
|
|
634
|
-
"requests_total": metrics.requests_total,
|
|
635
|
-
"errors_total": metrics.errors_total,
|
|
636
|
-
"avg_response_time": metrics.avg_response_time,
|
|
637
|
-
},
|
|
638
|
-
})
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
### 6. Testing MCP Servers
|
|
642
|
-
|
|
643
|
-
```python
|
|
644
|
-
import pytest
|
|
645
|
-
from fastmcp.testing import MCPTestClient
|
|
646
|
-
|
|
647
|
-
@pytest.fixture
|
|
648
|
-
def client():
|
|
649
|
-
"""Create test client for MCP server."""
|
|
650
|
-
return MCPTestClient(mcp)
|
|
651
|
-
|
|
652
|
-
class TestMCPServer:
|
|
653
|
-
async def test_tool_execution(self, client):
|
|
654
|
-
"""Test basic tool execution."""
|
|
655
|
-
result = await client.call_tool("get_weather", {"city": "Seattle"})
|
|
656
|
-
|
|
657
|
-
assert result.success
|
|
658
|
-
assert "Temperature" in result.content
|
|
659
|
-
assert "Seattle" in result.content
|
|
660
|
-
|
|
661
|
-
async def test_tool_validation(self, client):
|
|
662
|
-
"""Test parameter validation."""
|
|
663
|
-
result = await client.call_tool("get_weather", {})
|
|
664
|
-
|
|
665
|
-
assert not result.success
|
|
666
|
-
assert "city" in result.error.lower()
|
|
667
|
-
|
|
668
|
-
async def test_resource_access(self, client):
|
|
669
|
-
"""Test resource retrieval."""
|
|
670
|
-
result = await client.read_resource("config://database")
|
|
671
|
-
|
|
672
|
-
assert result.success
|
|
673
|
-
data = json.loads(result.content)
|
|
674
|
-
assert "host" in data
|
|
675
|
-
assert "port" in data
|
|
676
|
-
|
|
677
|
-
async def test_dynamic_resource(self, client):
|
|
678
|
-
"""Test dynamic resource with parameters."""
|
|
679
|
-
result = await client.read_resource("table://public/users/schema")
|
|
680
|
-
|
|
681
|
-
assert result.success
|
|
682
|
-
schema = json.loads(result.content)
|
|
683
|
-
assert schema["table"] == "users"
|
|
684
|
-
assert "columns" in schema
|
|
685
|
-
|
|
686
|
-
async def test_error_handling(self, client):
|
|
687
|
-
"""Test error responses."""
|
|
688
|
-
result = await client.call_tool("safe_operation", {
|
|
689
|
-
"operation": "invalid",
|
|
690
|
-
"params": {},
|
|
691
|
-
})
|
|
692
|
-
|
|
693
|
-
assert result.content["success"] is False
|
|
694
|
-
assert "error" in result.content
|
|
695
|
-
|
|
696
|
-
# Integration test with mock
|
|
697
|
-
class TestMCPIntegration:
|
|
698
|
-
async def test_full_workflow(self, client, mock_db):
|
|
699
|
-
"""Test complete workflow."""
|
|
700
|
-
# Setup test data
|
|
701
|
-
mock_db.insert_users([
|
|
702
|
-
{"id": 1, "name": "Alice"},
|
|
703
|
-
{"id": 2, "name": "Bob"},
|
|
704
|
-
])
|
|
705
|
-
|
|
706
|
-
# Search users
|
|
707
|
-
result = await client.call_tool("search_database", {
|
|
708
|
-
"query": "Alice",
|
|
709
|
-
"table": "users",
|
|
710
|
-
})
|
|
711
|
-
|
|
712
|
-
assert result.success
|
|
713
|
-
users = json.loads(result.content)
|
|
714
|
-
assert len(users) == 1
|
|
715
|
-
assert users[0]["name"] == "Alice"
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
## Use Cases
|
|
719
|
-
|
|
720
|
-
### 1. Database Integration Server
|
|
721
|
-
|
|
722
|
-
```python
|
|
723
|
-
# Complete database integration MCP server
|
|
724
|
-
from fastmcp import FastMCP
|
|
725
104
|
import asyncpg
|
|
726
105
|
|
|
727
106
|
mcp = FastMCP("database-server")
|
|
728
|
-
|
|
729
|
-
# Connection pool
|
|
730
107
|
pool: asyncpg.Pool = None
|
|
731
108
|
|
|
732
109
|
@mcp.tool()
|
|
@@ -743,38 +120,19 @@ async def query(sql: str, params: list = None) -> dict:
|
|
|
743
120
|
"count": len(rows),
|
|
744
121
|
}
|
|
745
122
|
|
|
746
|
-
@mcp.tool()
|
|
747
|
-
async def describe_table(table_name: str) -> dict:
|
|
748
|
-
"""Get table structure."""
|
|
749
|
-
async with pool.acquire() as conn:
|
|
750
|
-
columns = await conn.fetch("""
|
|
751
|
-
SELECT column_name, data_type, is_nullable
|
|
752
|
-
FROM information_schema.columns
|
|
753
|
-
WHERE table_name = $1
|
|
754
|
-
""", table_name)
|
|
755
|
-
|
|
756
|
-
return {
|
|
757
|
-
"table": table_name,
|
|
758
|
-
"columns": [dict(col) for col in columns],
|
|
759
|
-
}
|
|
760
|
-
|
|
761
123
|
@mcp.resource("schema://tables")
|
|
762
124
|
async def list_tables() -> str:
|
|
763
125
|
"""List all database tables."""
|
|
764
126
|
async with pool.acquire() as conn:
|
|
765
|
-
tables = await conn.fetch(
|
|
766
|
-
SELECT table_name
|
|
767
|
-
|
|
768
|
-
WHERE table_schema = 'public'
|
|
769
|
-
""")
|
|
127
|
+
tables = await conn.fetch(
|
|
128
|
+
"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"
|
|
129
|
+
)
|
|
770
130
|
return json.dumps([t["table_name"] for t in tables])
|
|
771
131
|
```
|
|
772
132
|
|
|
773
|
-
###
|
|
133
|
+
### Secure File System Server
|
|
774
134
|
|
|
775
135
|
```python
|
|
776
|
-
# Secure file system access server
|
|
777
|
-
from fastmcp import FastMCP
|
|
778
136
|
from pathlib import Path
|
|
779
137
|
|
|
780
138
|
mcp = FastMCP("filesystem-server")
|
|
@@ -787,95 +145,64 @@ def validate_path(path: str) -> Path:
|
|
|
787
145
|
raise ValueError("Path outside workspace")
|
|
788
146
|
return full_path
|
|
789
147
|
|
|
790
|
-
@mcp.tool()
|
|
791
|
-
def list_directory(path: str = ".") -> list[dict]:
|
|
792
|
-
"""List directory contents."""
|
|
793
|
-
dir_path = validate_path(path)
|
|
794
|
-
|
|
795
|
-
return [
|
|
796
|
-
{
|
|
797
|
-
"name": item.name,
|
|
798
|
-
"type": "directory" if item.is_dir() else "file",
|
|
799
|
-
"size": item.stat().st_size if item.is_file() else None,
|
|
800
|
-
"modified": item.stat().st_mtime,
|
|
801
|
-
}
|
|
802
|
-
for item in dir_path.iterdir()
|
|
803
|
-
]
|
|
804
|
-
|
|
805
148
|
@mcp.tool()
|
|
806
149
|
def read_file(path: str) -> str:
|
|
807
|
-
"""Read file contents."""
|
|
150
|
+
"""Read file contents safely."""
|
|
808
151
|
file_path = validate_path(path)
|
|
809
152
|
return file_path.read_text()
|
|
810
153
|
|
|
811
154
|
@mcp.tool()
|
|
812
|
-
def
|
|
813
|
-
"""
|
|
814
|
-
|
|
815
|
-
return [
|
|
816
|
-
str(p.relative_to(WORKSPACE))
|
|
817
|
-
for p in search_path.rglob(pattern)
|
|
818
|
-
]
|
|
155
|
+
def list_directory(path: str = ".") -> list[dict]:
|
|
156
|
+
"""List directory contents."""
|
|
157
|
+
dir_path = validate_path(path)
|
|
158
|
+
return [{"name": f.name, "type": "dir" if f.is_dir() else "file"} for f in dir_path.iterdir()]
|
|
819
159
|
```
|
|
820
160
|
|
|
821
|
-
|
|
161
|
+
### Testing MCP Servers
|
|
822
162
|
|
|
823
|
-
|
|
163
|
+
```python
|
|
164
|
+
import pytest
|
|
165
|
+
from fastmcp.testing import MCPTestClient
|
|
824
166
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
- **Use async** - For I/O-bound operations
|
|
829
|
-
- **Test comprehensively** - Use MCPTestClient for testing
|
|
830
|
-
- **Follow MCP spec** - Adhere to protocol specification
|
|
167
|
+
@pytest.fixture
|
|
168
|
+
def client():
|
|
169
|
+
return MCPTestClient(mcp)
|
|
831
170
|
|
|
832
|
-
|
|
171
|
+
class TestMCPServer:
|
|
172
|
+
async def test_tool_execution(self, client):
|
|
173
|
+
result = await client.call_tool("get_weather", {"city": "Seattle"})
|
|
174
|
+
assert result.success
|
|
175
|
+
assert "Seattle" in result.content
|
|
833
176
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
177
|
+
async def test_resource_access(self, client):
|
|
178
|
+
result = await client.read_resource("config://settings")
|
|
179
|
+
assert result.success
|
|
180
|
+
data = json.loads(result.content)
|
|
181
|
+
assert "theme" in data
|
|
182
|
+
```
|
|
840
183
|
|
|
841
|
-
|
|
184
|
+
## Best Practices
|
|
842
185
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
"""Ensure path is within allowed directory."""
|
|
856
|
-
resolved = (allowed_root / path).resolve()
|
|
857
|
-
if not str(resolved).startswith(str(allowed_root.resolve())):
|
|
858
|
-
raise SecurityError("Path traversal attempt detected")
|
|
859
|
-
return resolved
|
|
860
|
-
|
|
861
|
-
@staticmethod
|
|
862
|
-
def sanitize_output(data: dict) -> dict:
|
|
863
|
-
"""Remove sensitive fields from output."""
|
|
864
|
-
sensitive_keys = {"password", "secret", "token", "api_key"}
|
|
865
|
-
return {
|
|
866
|
-
k: v for k, v in data.items()
|
|
867
|
-
if k.lower() not in sensitive_keys
|
|
868
|
-
}
|
|
869
|
-
```
|
|
186
|
+
| Do | Avoid |
|
|
187
|
+
|----|-------|
|
|
188
|
+
| Document tools thoroughly with clear docstrings | Vague or missing tool descriptions |
|
|
189
|
+
| Validate all inputs with type hints | Trusting user input without validation |
|
|
190
|
+
| Return structured error responses | Exposing internal error details |
|
|
191
|
+
| Use async for I/O-bound operations | Blocking the event loop with sync I/O |
|
|
192
|
+
| Implement pagination for large results | Returning unbounded data sets |
|
|
193
|
+
| Add rate limiting for resource-intensive tools | Allowing unlimited API calls |
|
|
194
|
+
| Test tools with MCPTestClient | Skipping unit tests for tools |
|
|
195
|
+
| Follow MCP specification strictly | Deviating from protocol standards |
|
|
196
|
+
| Sanitize paths and SQL queries | Allowing path traversal or SQL injection |
|
|
197
|
+
| Log tool calls for debugging | Missing audit trail for operations |
|
|
870
198
|
|
|
871
199
|
## Related Skills
|
|
872
200
|
|
|
873
201
|
- **python** - Primary language for FastMCP
|
|
874
202
|
- **typescript** - MCP SDK for TypeScript
|
|
875
203
|
- **api-architecture** - API design patterns
|
|
876
|
-
- **backend-development** - Server development patterns
|
|
877
204
|
|
|
878
|
-
##
|
|
205
|
+
## References
|
|
879
206
|
|
|
880
207
|
- [MCP Specification](https://spec.modelcontextprotocol.io/)
|
|
881
208
|
- [FastMCP Documentation](https://github.com/jlowin/fastmcp)
|