codingbuddy-rules 4.3.0 → 4.4.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/.ai-rules/adapters/antigravity.md +648 -160
- package/.ai-rules/adapters/codex.md +500 -10
- package/.ai-rules/adapters/cursor.md +252 -8
- package/.ai-rules/adapters/kiro.md +551 -93
- package/.ai-rules/adapters/opencode-skills.md +179 -188
- package/.ai-rules/adapters/opencode.md +245 -44
- package/.ai-rules/skills/README.md +92 -24
- package/.ai-rules/skills/agent-design/SKILL.md +269 -0
- package/.ai-rules/skills/code-explanation/SKILL.md +259 -0
- package/.ai-rules/skills/context-management/SKILL.md +244 -0
- package/.ai-rules/skills/deployment-checklist/SKILL.md +233 -0
- package/.ai-rules/skills/documentation-generation/SKILL.md +293 -0
- package/.ai-rules/skills/error-analysis/SKILL.md +250 -0
- package/.ai-rules/skills/legacy-modernization/SKILL.md +292 -0
- package/.ai-rules/skills/mcp-builder/SKILL.md +356 -0
- package/.ai-rules/skills/prompt-engineering/SKILL.md +318 -0
- package/.ai-rules/skills/rule-authoring/SKILL.md +273 -0
- package/.ai-rules/skills/security-audit/SKILL.md +241 -0
- package/.ai-rules/skills/tech-debt/SKILL.md +224 -0
- package/package.json +1 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: legacy-modernization
|
|
3
|
+
description: Use when modernizing legacy code or migrating outdated patterns to current best practices. Covers assessment, strangler fig pattern, incremental migration, and risk management.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Legacy Modernization
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Legacy code isn't bad code — it's code that solved a real problem when it was written. Modernization is the process of adapting it to current requirements without breaking what works.
|
|
11
|
+
|
|
12
|
+
**Core principle:** Never rewrite from scratch. Strangle it incrementally. Each step must leave the system in a working state.
|
|
13
|
+
|
|
14
|
+
**Iron Law:**
|
|
15
|
+
```
|
|
16
|
+
THE SYSTEM MUST WORK AFTER EVERY CHANGE
|
|
17
|
+
There is no "temporarily broken while we modernize"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## When to Use
|
|
21
|
+
|
|
22
|
+
- Migrating from CommonJS to ESM
|
|
23
|
+
- Upgrading major framework versions (NestJS 9 → 10)
|
|
24
|
+
- Replacing callback patterns with async/await
|
|
25
|
+
- Moving from JavaScript to TypeScript
|
|
26
|
+
- Replacing deprecated APIs
|
|
27
|
+
- Architectural migration (monolith → modules)
|
|
28
|
+
|
|
29
|
+
## Assessment Phase
|
|
30
|
+
|
|
31
|
+
### Inventory
|
|
32
|
+
|
|
33
|
+
Before any changes, understand what you have:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Size and complexity
|
|
37
|
+
find src/ -name "*.ts" -o -name "*.js" | \
|
|
38
|
+
xargs wc -l | sort -rn | head -20
|
|
39
|
+
|
|
40
|
+
# Dependency graph
|
|
41
|
+
npx madge --circular src/
|
|
42
|
+
|
|
43
|
+
# Test coverage (what's protected)
|
|
44
|
+
npx jest --coverage --coverageReporters text-summary
|
|
45
|
+
|
|
46
|
+
# Outdated packages
|
|
47
|
+
npm outdated
|
|
48
|
+
|
|
49
|
+
# Known patterns to modernize
|
|
50
|
+
grep -rn "require\(" src/ --include="*.ts" | wc -l # CommonJS in TypeScript
|
|
51
|
+
grep -rn "callback\|\.then\|\.catch" src/ --include="*.ts" | wc -l # Promises vs async
|
|
52
|
+
grep -rn ": any" src/ --include="*.ts" | wc -l # Untyped code
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Risk Assessment
|
|
56
|
+
|
|
57
|
+
Rate each area for modernization risk:
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
## Modernization Risk Register
|
|
61
|
+
|
|
62
|
+
| Module | Lines | Test Coverage | External Deps | Criticality | Risk |
|
|
63
|
+
|--------|-------|---------------|---------------|-------------|------|
|
|
64
|
+
| rules.service.ts | 120 | 85% | None | High | LOW |
|
|
65
|
+
| mcp.service.ts | 450 | 45% | MCP SDK | High | HIGH |
|
|
66
|
+
| main.ts | 60 | 0% | NestJS | Medium | MEDIUM |
|
|
67
|
+
|
|
68
|
+
Risk = (Criticality × (100 - Coverage%)) / 100
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Modernization Backlog
|
|
72
|
+
|
|
73
|
+
```markdown
|
|
74
|
+
## Modernization Items
|
|
75
|
+
|
|
76
|
+
| ID | Pattern | Count | Files | Effort |
|
|
77
|
+
|----|---------|-------|-------|--------|
|
|
78
|
+
| M-001 | `any` type → explicit types | 23 | 8 files | 2h |
|
|
79
|
+
| M-002 | Callbacks → async/await | 12 | 4 files | 4h |
|
|
80
|
+
| M-003 | CommonJS → ESM imports | 45 | 15 files | 1h |
|
|
81
|
+
| M-004 | NestJS 9 → NestJS 10 | 1 | package.json | 8h |
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Migration Strategies
|
|
85
|
+
|
|
86
|
+
### Strategy 1: Strangler Fig (Recommended)
|
|
87
|
+
|
|
88
|
+
Build new behavior alongside old behavior, gradually replacing old with new.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Phase 1: New implementation behind feature flag
|
|
92
|
+
async getRules(): Promise<Rule[]> {
|
|
93
|
+
if (process.env.USE_NEW_RULES_ENGINE === 'true') {
|
|
94
|
+
return this.newRulesEngine.getRules(); // New implementation
|
|
95
|
+
}
|
|
96
|
+
return this.legacyGetRules(); // Old implementation still works
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Phase 2: Enable in staging, verify
|
|
100
|
+
// Phase 3: Enable in production (10% → 50% → 100%)
|
|
101
|
+
// Phase 4: Remove old implementation
|
|
102
|
+
async getRules(): Promise<Rule[]> {
|
|
103
|
+
return this.newRulesEngine.getRules(); // Old code removed
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Strategy 2: Branch by Abstraction
|
|
108
|
+
|
|
109
|
+
1. Create abstraction over legacy code
|
|
110
|
+
2. Implement new code behind abstraction
|
|
111
|
+
3. Switch abstraction to new code
|
|
112
|
+
4. Remove old code
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Step 1: Introduce interface
|
|
116
|
+
interface RulesEngine {
|
|
117
|
+
getRules(): Promise<Rule[]>;
|
|
118
|
+
searchRules(query: string): Promise<Rule[]>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Step 2: Wrap legacy code
|
|
122
|
+
class LegacyRulesEngine implements RulesEngine {
|
|
123
|
+
// Existing code, now behind interface
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Step 3: New implementation
|
|
127
|
+
class NewRulesEngine implements RulesEngine {
|
|
128
|
+
// Modern implementation
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Step 4: Switch (one line change)
|
|
132
|
+
const rulesEngine: RulesEngine = new NewRulesEngine();
|
|
133
|
+
// Was: new LegacyRulesEngine()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Common Modernization Patterns
|
|
137
|
+
|
|
138
|
+
### JavaScript → TypeScript
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// Before (JavaScript)
|
|
142
|
+
function getUser(id) {
|
|
143
|
+
return fetch('/api/users/' + id)
|
|
144
|
+
.then(function(response) {
|
|
145
|
+
return response.json();
|
|
146
|
+
})
|
|
147
|
+
.then(function(data) {
|
|
148
|
+
return data.user;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// After (TypeScript + async/await)
|
|
155
|
+
async function getUser(id: string): Promise<User> {
|
|
156
|
+
const response = await fetch(`/api/users/${id}`);
|
|
157
|
+
const data: { user: User } = await response.json();
|
|
158
|
+
return data.user;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Callbacks → Async/Await
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
// Before (callbacks)
|
|
166
|
+
function readRule(path, callback) {
|
|
167
|
+
fs.readFile(path, 'utf8', function(err, content) {
|
|
168
|
+
if (err) return callback(err);
|
|
169
|
+
callback(null, parseRule(content));
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// After (async/await)
|
|
176
|
+
async function readRule(path: string): Promise<Rule> {
|
|
177
|
+
const content = await fs.promises.readFile(path, 'utf-8');
|
|
178
|
+
return parseRule(content);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Removing `any` Types
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Before
|
|
186
|
+
function processRule(rule: any): any {
|
|
187
|
+
return { name: rule.name, content: rule.content };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// After (step 1: define interface)
|
|
191
|
+
interface RuleInput {
|
|
192
|
+
name: string;
|
|
193
|
+
content: string;
|
|
194
|
+
[key: string]: unknown; // allow extra properties
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface ProcessedRule {
|
|
198
|
+
name: string;
|
|
199
|
+
content: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// After (step 2: type the function)
|
|
203
|
+
function processRule(rule: RuleInput): ProcessedRule {
|
|
204
|
+
return { name: rule.name, content: rule.content };
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Safe Migration Process
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
For EACH modernization item:
|
|
212
|
+
|
|
213
|
+
1. WRITE TESTS (if coverage < 80%)
|
|
214
|
+
- Tests must cover current behavior
|
|
215
|
+
- These tests protect against regression
|
|
216
|
+
|
|
217
|
+
2. MODERNIZE one file at a time
|
|
218
|
+
- Apply pattern change
|
|
219
|
+
- Keep behavior identical
|
|
220
|
+
|
|
221
|
+
3. RUN ALL TESTS
|
|
222
|
+
- All existing tests must still pass
|
|
223
|
+
- No "we'll fix the tests later"
|
|
224
|
+
|
|
225
|
+
4. COMMIT
|
|
226
|
+
- One commit per file or per pattern type
|
|
227
|
+
- Message: "refactor: migrate X to Y in Z"
|
|
228
|
+
|
|
229
|
+
5. VERIFY in staging before next item
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Version Upgrade Process (Major Versions)
|
|
233
|
+
|
|
234
|
+
```markdown
|
|
235
|
+
## NestJS 9 → 10 Migration Plan
|
|
236
|
+
|
|
237
|
+
### Preparation
|
|
238
|
+
- [ ] Read migration guide: https://docs.nestjs.com/migration-guide
|
|
239
|
+
- [ ] Test suite at 80%+ coverage
|
|
240
|
+
- [ ] Feature branch created
|
|
241
|
+
|
|
242
|
+
### Steps
|
|
243
|
+
1. [ ] Update peer dependencies first
|
|
244
|
+
2. [ ] Update NestJS packages
|
|
245
|
+
3. [ ] Fix breaking API changes (see migration guide)
|
|
246
|
+
4. [ ] Run test suite
|
|
247
|
+
5. [ ] Fix type errors
|
|
248
|
+
6. [ ] Test in staging
|
|
249
|
+
7. [ ] Deploy to production
|
|
250
|
+
|
|
251
|
+
### Rollback
|
|
252
|
+
- git revert to previous package.json
|
|
253
|
+
- npm install
|
|
254
|
+
- Deploy previous version
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Risk Mitigation
|
|
258
|
+
|
|
259
|
+
| Risk | Mitigation |
|
|
260
|
+
|------|-----------|
|
|
261
|
+
| Breaking working functionality | Write tests before modernizing |
|
|
262
|
+
| Long migration blocks features | Use strangler fig pattern |
|
|
263
|
+
| Merge conflicts | Small, frequent commits |
|
|
264
|
+
| Regression in edge cases | Test coverage for edge cases first |
|
|
265
|
+
| Team unfamiliar with new patterns | Document new patterns + pair programming |
|
|
266
|
+
|
|
267
|
+
## Red Flags — STOP
|
|
268
|
+
|
|
269
|
+
| Thought | Reality |
|
|
270
|
+
|---------|---------|
|
|
271
|
+
| "Let's rewrite it from scratch" | Rewrites take 3× longer and miss edge cases |
|
|
272
|
+
| "We'll fix tests later" | Tests are the safety net — fix them now |
|
|
273
|
+
| "One big refactoring PR" | Small PRs are safer and reviewable |
|
|
274
|
+
| "The new code is obviously correct" | Verify with tests, not confidence |
|
|
275
|
+
| "We can modernize while adding features" | Keep modernization commits separate |
|
|
276
|
+
|
|
277
|
+
## Quick Reference
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
Migration Patterns:
|
|
281
|
+
──────────────────────────────
|
|
282
|
+
Strangler Fig → New code wraps old, gradual replacement
|
|
283
|
+
Branch by Abstraction → Interface first, then implementations
|
|
284
|
+
Parallel Run → Old and new run simultaneously, compare results
|
|
285
|
+
Expand-Contract → Database: add new → migrate → remove old
|
|
286
|
+
|
|
287
|
+
Priority Order:
|
|
288
|
+
──────────────────────────────
|
|
289
|
+
1. Highest risk (low coverage, high criticality) → test first
|
|
290
|
+
2. Quick wins (high impact, low effort)
|
|
291
|
+
3. Architectural changes (last, requires stability)
|
|
292
|
+
```
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mcp-builder
|
|
3
|
+
description: Use when building or extending MCP (Model Context Protocol) servers. Covers NestJS-based server design, Tools/Resources/Prompts capability design, transport implementation (stdio/SSE), and testing strategies.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MCP Builder
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The Model Context Protocol (MCP) is the standard for connecting AI assistants to external tools, data, and prompts. Building a quality MCP server requires understanding the protocol's three capability types and implementing them with proper error handling and transport support.
|
|
11
|
+
|
|
12
|
+
**Core principle:** MCP servers are AI interfaces, not REST APIs. Design for machine consumption and LLM context efficiency.
|
|
13
|
+
|
|
14
|
+
**Iron Law:**
|
|
15
|
+
```
|
|
16
|
+
SCHEMA FIRST. Every Tool, Resource, and Prompt must have complete JSON Schema before implementation.
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- Creating a new MCP server from scratch
|
|
22
|
+
- Adding new Tools, Resources, or Prompts to an existing server
|
|
23
|
+
- Implementing new transport modes (stdio ↔ SSE)
|
|
24
|
+
- Testing MCP server capabilities
|
|
25
|
+
- Debugging MCP communication issues
|
|
26
|
+
|
|
27
|
+
## MCP Architecture Overview
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
MCP Client (Claude, Cursor, etc.)
|
|
31
|
+
↕ stdio / SSE
|
|
32
|
+
MCP Server
|
|
33
|
+
├── Tools → Functions the AI can call (side effects allowed)
|
|
34
|
+
├── Resources → Data the AI can read (read-only, URI-addressed)
|
|
35
|
+
└── Prompts → Reusable prompt templates
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## NestJS MCP Server Structure
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
apps/mcp-server/src/
|
|
42
|
+
├── main.ts # Transport setup (stdio vs SSE)
|
|
43
|
+
├── app.module.ts # Root module
|
|
44
|
+
├── mcp/
|
|
45
|
+
│ ├── mcp.module.ts # MCP module
|
|
46
|
+
│ ├── mcp.service.ts # MCP server registration
|
|
47
|
+
│ ├── tools/ # Tool handlers
|
|
48
|
+
│ ├── resources/ # Resource handlers
|
|
49
|
+
│ └── prompts/ # Prompt handlers
|
|
50
|
+
└── rules/
|
|
51
|
+
├── rules.module.ts
|
|
52
|
+
└── rules.service.ts # Business logic (pure)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Designing MCP Capabilities
|
|
56
|
+
|
|
57
|
+
### Tools (AI can call these)
|
|
58
|
+
|
|
59
|
+
Tools perform actions and return results. They CAN have side effects.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Tool Schema (define first)
|
|
63
|
+
const searchRulesTool = {
|
|
64
|
+
name: 'search_rules',
|
|
65
|
+
description: 'Search AI coding rules by keyword or topic. Returns matching rules with their content.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
query: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Search term to find relevant rules',
|
|
72
|
+
minLength: 1,
|
|
73
|
+
maxLength: 200,
|
|
74
|
+
},
|
|
75
|
+
limit: {
|
|
76
|
+
type: 'number',
|
|
77
|
+
description: 'Maximum results to return (default: 10)',
|
|
78
|
+
minimum: 1,
|
|
79
|
+
maximum: 50,
|
|
80
|
+
default: 10,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
required: ['query'],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Tool Handler
|
|
90
|
+
@Injectable()
|
|
91
|
+
export class SearchRulesHandler {
|
|
92
|
+
constructor(private readonly rulesService: RulesService) {}
|
|
93
|
+
|
|
94
|
+
async handle(params: { query: string; limit?: number }): Promise<ToolResult> {
|
|
95
|
+
// Validate input
|
|
96
|
+
if (!params.query?.trim()) {
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: 'text', text: 'Error: query is required' }],
|
|
99
|
+
isError: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const results = await this.rulesService.search(params.query, {
|
|
105
|
+
limit: params.limit ?? 10,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: 'text',
|
|
112
|
+
text: results.length === 0
|
|
113
|
+
? 'No rules found for: ' + params.query
|
|
114
|
+
: results.map(r => `## ${r.name}\n${r.content}`).join('\n\n'),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
121
|
+
isError: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Resources (AI can read these)
|
|
129
|
+
|
|
130
|
+
Resources are read-only data exposed via URI. Use for static/cacheable content.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Resource definition
|
|
134
|
+
const ruleResource = {
|
|
135
|
+
uri: 'rules://core',
|
|
136
|
+
name: 'Core Rules',
|
|
137
|
+
description: 'Core workflow rules (PLAN/ACT/EVAL modes)',
|
|
138
|
+
mimeType: 'text/markdown',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Resource handler
|
|
142
|
+
async readResource(uri: string): Promise<ResourceContent> {
|
|
143
|
+
const ruleName = uri.replace('rules://', '');
|
|
144
|
+
const content = await this.rulesService.getRule(ruleName);
|
|
145
|
+
|
|
146
|
+
if (!content) {
|
|
147
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
uri,
|
|
152
|
+
mimeType: 'text/markdown',
|
|
153
|
+
text: content,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Resource URI design:**
|
|
159
|
+
```
|
|
160
|
+
rules://core → Core rules file
|
|
161
|
+
rules://agents/list → List of agents
|
|
162
|
+
agents://planner → Specific agent definition
|
|
163
|
+
checklists://security → Security checklist
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Prompts (Reusable templates)
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const activateAgentPrompt = {
|
|
170
|
+
name: 'activate_agent',
|
|
171
|
+
description: 'Generate activation prompt for a specialist agent',
|
|
172
|
+
arguments: [
|
|
173
|
+
{
|
|
174
|
+
name: 'agentName',
|
|
175
|
+
description: 'Name of the agent to activate (e.g., "solution-architect")',
|
|
176
|
+
required: true,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'task',
|
|
180
|
+
description: 'Task description for the agent',
|
|
181
|
+
required: false,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
async getPrompt(name: string, args: Record<string, string>): Promise<PromptResult> {
|
|
187
|
+
if (name === 'activate_agent') {
|
|
188
|
+
const agent = await this.agentsService.getAgent(args.agentName);
|
|
189
|
+
return {
|
|
190
|
+
messages: [
|
|
191
|
+
{
|
|
192
|
+
role: 'user',
|
|
193
|
+
content: {
|
|
194
|
+
type: 'text',
|
|
195
|
+
text: `Activate the ${agent.displayName} specialist.\n\n${agent.systemPrompt}\n\n${args.task ?? ''}`,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Transport Implementation
|
|
206
|
+
|
|
207
|
+
### Stdio Transport (Default)
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// main.ts
|
|
211
|
+
async function bootstrap() {
|
|
212
|
+
const transport = process.env.MCP_TRANSPORT ?? 'stdio';
|
|
213
|
+
|
|
214
|
+
if (transport === 'stdio') {
|
|
215
|
+
const app = await NestFactory.createApplicationContext(AppModule);
|
|
216
|
+
const mcpService = app.get(McpService);
|
|
217
|
+
await mcpService.startStdio();
|
|
218
|
+
} else {
|
|
219
|
+
const app = await NestFactory.create(AppModule);
|
|
220
|
+
await app.listen(process.env.PORT ?? 3000);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### SSE Transport (HTTP)
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// SSE endpoint with optional auth
|
|
229
|
+
@Get('/sse')
|
|
230
|
+
@UseGuards(SseAuthGuard)
|
|
231
|
+
async sse(@Req() req: Request, @Res() res: Response) {
|
|
232
|
+
const transport = new SSEServerTransport('/messages', res);
|
|
233
|
+
await this.mcpService.connect(transport);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@Post('/messages')
|
|
237
|
+
@UseGuards(SseAuthGuard)
|
|
238
|
+
async messages(@Req() req: Request, @Res() res: Response) {
|
|
239
|
+
await this.mcpService.handleMessage(req, res);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Auth guard
|
|
245
|
+
@Injectable()
|
|
246
|
+
export class SseAuthGuard implements CanActivate {
|
|
247
|
+
canActivate(context: ExecutionContext): boolean {
|
|
248
|
+
const token = process.env.MCP_SSE_TOKEN;
|
|
249
|
+
if (!token) return true; // Auth disabled if token not set
|
|
250
|
+
|
|
251
|
+
const request = context.switchToHttp().getRequest();
|
|
252
|
+
const provided = request.headers.authorization?.replace('Bearer ', '');
|
|
253
|
+
return provided === token;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Testing Strategy
|
|
259
|
+
|
|
260
|
+
### Unit Tests (Tools/Resources)
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
describe('SearchRulesHandler', () => {
|
|
264
|
+
let handler: SearchRulesHandler;
|
|
265
|
+
let rulesService: RulesService;
|
|
266
|
+
|
|
267
|
+
beforeEach(() => {
|
|
268
|
+
rulesService = new RulesService('./test-fixtures/.ai-rules');
|
|
269
|
+
handler = new SearchRulesHandler(rulesService);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('returns results for valid query', async () => {
|
|
273
|
+
const result = await handler.handle({ query: 'TDD' });
|
|
274
|
+
expect(result.isError).toBeUndefined();
|
|
275
|
+
expect(result.content[0].text).toContain('TDD');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('returns error for empty query', async () => {
|
|
279
|
+
const result = await handler.handle({ query: '' });
|
|
280
|
+
expect(result.isError).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Integration Tests (Protocol Level)
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
289
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
290
|
+
|
|
291
|
+
describe('MCP Server Integration', () => {
|
|
292
|
+
let client: Client;
|
|
293
|
+
|
|
294
|
+
beforeAll(async () => {
|
|
295
|
+
const transport = new StdioClientTransport({
|
|
296
|
+
command: 'node',
|
|
297
|
+
args: ['dist/main.js'],
|
|
298
|
+
});
|
|
299
|
+
client = new Client({ name: 'test', version: '1.0.0' }, {});
|
|
300
|
+
await client.connect(transport);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('lists available tools', async () => {
|
|
304
|
+
const { tools } = await client.listTools();
|
|
305
|
+
expect(tools.map(t => t.name)).toContain('search_rules');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('calls search_rules tool', async () => {
|
|
309
|
+
const result = await client.callTool({
|
|
310
|
+
name: 'search_rules',
|
|
311
|
+
arguments: { query: 'TDD' },
|
|
312
|
+
});
|
|
313
|
+
expect(result.isError).toBeFalsy();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Design Checklist
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
Tool Design:
|
|
322
|
+
- [ ] Name is a verb or verb_noun (search_rules, get_agent)
|
|
323
|
+
- [ ] Description explains WHAT and WHEN (for LLM to understand)
|
|
324
|
+
- [ ] All parameters have descriptions and types
|
|
325
|
+
- [ ] Required vs optional params clearly marked
|
|
326
|
+
- [ ] Error responses use isError: true with descriptive messages
|
|
327
|
+
- [ ] Input validated before processing
|
|
328
|
+
|
|
329
|
+
Resource Design:
|
|
330
|
+
- [ ] URI follows scheme://path pattern
|
|
331
|
+
- [ ] mimeType set correctly (text/markdown, application/json)
|
|
332
|
+
- [ ] Read-only (no side effects)
|
|
333
|
+
- [ ] Returns 404-equivalent for missing resources
|
|
334
|
+
|
|
335
|
+
Prompt Design:
|
|
336
|
+
- [ ] Arguments have clear descriptions
|
|
337
|
+
- [ ] Output is ready-to-use for the LLM
|
|
338
|
+
- [ ] Does not duplicate Tool functionality
|
|
339
|
+
|
|
340
|
+
Server:
|
|
341
|
+
- [ ] Stdio and SSE transports both tested
|
|
342
|
+
- [ ] Auth guard works when MCP_SSE_TOKEN is set
|
|
343
|
+
- [ ] Auth disabled when MCP_SSE_TOKEN is unset
|
|
344
|
+
- [ ] Error handling prevents server crashes
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Common Mistakes
|
|
348
|
+
|
|
349
|
+
| Mistake | Fix |
|
|
350
|
+
|---------|-----|
|
|
351
|
+
| Tool does too many things | One Tool = one action |
|
|
352
|
+
| Resource has side effects | Move to Tool if it changes state |
|
|
353
|
+
| Schema has no descriptions | LLM uses descriptions to decide when/how to call |
|
|
354
|
+
| No error handling in tools | Always catch and return isError: true |
|
|
355
|
+
| Breaking stdio with console.log | Use stderr for logs: `process.stderr.write(...)` |
|
|
356
|
+
| Hardcoded paths | Use env vars or config injection |
|