omgkit 2.2.0 → 2.3.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.
Files changed (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: mcp-development
3
- description: Create Model Context Protocol servers, tools, and resources for AI assistant integration
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 Development
15
+ # Developing MCP Servers
16
16
 
17
- Create **Model Context Protocol (MCP) servers** that extend AI assistants like Claude with custom tools, resources, and prompts. This skill covers server architecture, tool design, and production deployment patterns.
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(load_settings(), indent=2)
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
- ### 2. TypeScript MCP Server
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
- import {
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
- name: "my-mcp-server",
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
- name: "file_search",
180
- description: "Search for files matching a query",
181
- inputSchema: {
182
- type: "object",
183
- properties: {
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
- try {
220
- switch (name) {
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
- // Start server
316
- async function main() {
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
- ### 4. Resource Management
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
- FROM information_schema.tables
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
- ### 2. File System Server
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 search_files(pattern: str, path: str = ".") -> list[str]:
813
- """Search for files matching pattern."""
814
- search_path = validate_path(path)
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
- ## Best Practices
161
+ ### Testing MCP Servers
822
162
 
823
- ### Do's
163
+ ```python
164
+ import pytest
165
+ from fastmcp.testing import MCPTestClient
824
166
 
825
- - **Document thoroughly** - Clear docstrings become tool descriptions
826
- - **Validate inputs** - Use type hints and validation
827
- - **Handle errors gracefully** - Return structured error responses
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
- ### Don'ts
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
- - Don't expose sensitive operations without authorization
835
- - Don't allow arbitrary code execution
836
- - Don't return unbounded data (use pagination)
837
- - Don't ignore rate limiting for resource-intensive tools
838
- - Don't skip input sanitization
839
- - Don't expose internal error details
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
- ### Security Checklist
184
+ ## Best Practices
842
185
 
843
- ```python
844
- # Security validation helper
845
- class SecurityValidator:
846
- @staticmethod
847
- def validate_sql(query: str) -> bool:
848
- """Ensure SQL is read-only."""
849
- dangerous = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "TRUNCATE"]
850
- upper = query.upper()
851
- return not any(d in upper for d in dangerous)
852
-
853
- @staticmethod
854
- def validate_path(path: str, allowed_root: Path) -> Path:
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
- ## Reference Resources
205
+ ## References
879
206
 
880
207
  - [MCP Specification](https://spec.modelcontextprotocol.io/)
881
208
  - [FastMCP Documentation](https://github.com/jlowin/fastmcp)