@ema.co/mcp-toolkit 2026.2.19 → 2026.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.

@@ -0,0 +1,408 @@
1
+ /**
2
+ * Validation Rules Generators for MCP Resources
3
+ *
4
+ * Generates LLM-friendly validation rules and best practices checklists
5
+ * from the canonical SDK rule definitions.
6
+ *
7
+ * Extracted from resources.ts for maintainability.
8
+ */
9
+ import { INPUT_SOURCE_RULES, ANTI_PATTERNS, OPTIMIZATION_RULES } from "./domain/validation-rules.js";
10
+ import { STRUCTURAL_INVARIANTS } from "./domain/structural-rules.js";
11
+ /**
12
+ * Generate validation rules in LLM-friendly Markdown format.
13
+ * Includes rules from Go validator: required outputs, multiple writers, response/abstain, category hierarchy.
14
+ */
15
+ export function generateValidationRulesForLLM() {
16
+ return `# Workflow Validation Rules
17
+
18
+ > **Use these rules DURING workflow generation to avoid errors proactively.**
19
+
20
+ These rules are extracted from the Go validator's static validation logic. Follow them when generating or modifying workflows.
21
+
22
+ ## Rule 1: Required Output on All Paths
23
+
24
+ **Rule ID**: \`required_output_all_paths\`
25
+ **Severity**: Critical
26
+ **Applies To**: Named results, execution paths
27
+
28
+ ### Logic
29
+
30
+ **Algorithm**: Enumerate all execution paths. For each path, check if all named results are produced.
31
+
32
+ **Steps**:
33
+ 1. Enumerate all paths from trigger to completion
34
+ 2. For each path, collect all outputs produced
35
+ 3. For each named result, check if it's in path outputs
36
+ 4. If missing on any path → error
37
+
38
+ **Complexity**: O(P * R) where P = paths, R = named results
39
+
40
+ ### Rules
41
+
42
+ **Constraint**: Every execution path must produce all named results.
43
+
44
+ **Why**: Named results are contract guarantees. If a path doesn't produce a required output, the contract is violated.
45
+
46
+ ### Examples
47
+
48
+ **Bad Pattern**:
49
+ \`\`\`json
50
+ {
51
+ "path": ["trigger", "categorizer", "billing_branch", "search"],
52
+ "named_result": "response_with_sources",
53
+ "produces": [] // Missing response
54
+ }
55
+ \`\`\`
56
+
57
+ **Why Bad**: Path ends without producing required output.
58
+
59
+ **Good Pattern**:
60
+ \`\`\`json
61
+ {
62
+ "path": ["trigger", "categorizer", "billing_branch", "search", "respond_for_external_actions"],
63
+ "named_result": "response",
64
+ "produces": ["response"] // ✅ Produced
65
+ }
66
+ \`\`\`
67
+
68
+ **Why Good**: All paths produce required output.
69
+
70
+ ### Remediation
71
+
72
+ **How to Fix**:
73
+ 1. Identify the path missing the output
74
+ 2. Add a node that produces the required output
75
+ 3. Connect the node's output to WORKFLOW_OUTPUT
76
+
77
+ **Common Fixes**:
78
+ - Add \`respond_for_external_actions\` node after search
79
+ - Add \`call_llm\` node for text generation
80
+ - Add \`fixed_response\` for static responses
81
+
82
+ **Prevention** (During Generation):
83
+ - Always ensure every categorizer branch has a response node
84
+ - Check that all paths end with nodes connected to WORKFLOW_OUTPUT
85
+ - Verify resultMappings include all required outputs
86
+
87
+ ---
88
+
89
+ ## Rule 2: Single Writer Per Output
90
+
91
+ **Rule ID**: \`single_writer_per_output\`
92
+ **Severity**: Critical
93
+ **Applies To**: Named results, execution paths
94
+
95
+ ### Logic
96
+
97
+ **Algorithm**: For each named result, count how many nodes produce it on each path.
98
+
99
+ **Steps**:
100
+ 1. For each named result
101
+ 2. For each execution path
102
+ 3. Count nodes that produce this result
103
+ 4. If count > 1 on any path → error
104
+
105
+ ### Rules
106
+
107
+ **Constraint**: A single named result can only have one producer per execution path.
108
+
109
+ **Why**: Multiple producers on the same path cause ambiguity about which value to use.
110
+
111
+ ### Examples
112
+
113
+ **Bad Pattern**:
114
+ \`\`\`json
115
+ {
116
+ "path": ["trigger", "categorizer", "billing"],
117
+ "named_result": "response_with_sources",
118
+ "producers": ["respond_billing", "respond_general"] // ❌ Two producers
119
+ }
120
+ \`\`\`
121
+
122
+ **Why Bad**: Two nodes produce the same output on the same path.
123
+
124
+ **Good Pattern**:
125
+ \`\`\`json
126
+ {
127
+ "path": ["trigger", "categorizer", "billing"],
128
+ "named_result": "response_with_sources",
129
+ "producers": ["respond_billing"] // ✅ Single producer
130
+ }
131
+ \`\`\`
132
+
133
+ **Why Good**: Only one node produces the output.
134
+
135
+ ### Remediation
136
+
137
+ **How to Fix**:
138
+ 1. Identify which nodes produce the same output
139
+ 2. Remove one producer or use different named results
140
+ 3. Ensure only one node produces each named result per path
141
+
142
+ **Prevention** (During Generation):
143
+ - Use mutually exclusive runIf conditions to gate responders
144
+ - Ensure only one response node executes per category branch
145
+
146
+ ---
147
+
148
+ ## Rule 3: Response or Abstain Required
149
+
150
+ **Rule ID**: \`response_or_abstain_required\`
151
+ **Severity**: Critical
152
+ **Applies To**: Execution paths
153
+
154
+ ### Logic
155
+
156
+ **Algorithm**: For each execution path, check if it produces a response or has an abstain reason.
157
+
158
+ **Steps**:
159
+ 1. For each completed path
160
+ 2. Check if path produces any response output
161
+ 3. Check if path has abstain reason
162
+ 4. If neither → error
163
+
164
+ ### Rules
165
+
166
+ **Constraint**: Every execution path must either produce a user-visible response or explicitly abstain with a reason.
167
+
168
+ **Why**: Users expect responses. Paths that don't respond and don't explain why create poor UX.
169
+
170
+ ### Examples
171
+
172
+ **Bad Pattern**:
173
+ \`\`\`json
174
+ {
175
+ "path": ["trigger", "categorizer", "billing", "search"],
176
+ "has_response": false,
177
+ "has_abstain": false // ❌ Neither
178
+ }
179
+ \`\`\`
180
+
181
+ **Why Bad**: Path ends without response or abstain reason.
182
+
183
+ **Good Pattern**:
184
+ \`\`\`json
185
+ {
186
+ "path": ["trigger", "categorizer", "billing", "search", "respond_for_external_actions"],
187
+ "has_response": true // ✅ Has response
188
+ }
189
+ \`\`\`
190
+
191
+ **Why Good**: Path produces a response.
192
+
193
+ ### Remediation
194
+
195
+ **How to Fix**:
196
+ 1. Add a response node to the path, OR
197
+ 2. Add an abstain reason to the workflow configuration
198
+
199
+ **Prevention** (During Generation):
200
+ - Always add response nodes to every categorizer branch
201
+ - If a path shouldn't respond, document why with abstain reason
202
+
203
+ ---
204
+
205
+ ## Rule 4: Category Hierarchy Valid
206
+
207
+ **Rule ID**: \`category_hierarchy_valid\`
208
+ **Severity**: Critical
209
+ **Applies To**: Categorizers
210
+
211
+ ### Logic
212
+
213
+ **Algorithm**: Validate categorizer structure and category definitions.
214
+
215
+ **Steps**:
216
+ 1. Check categorizer has Fallback category
217
+ 2. Check category hierarchy rules (TBD - need Go validator analysis)
218
+ 3. Validate category → handler mapping
219
+
220
+ ### Rules
221
+
222
+ **Constraint**: Categorizers must have proper structure and hierarchy.
223
+
224
+ **Why**: Invalid category structure causes routing failures.
225
+
226
+ ### Examples
227
+
228
+ **Bad Pattern**:
229
+ \`\`\`json
230
+ {
231
+ "categorizer": {
232
+ "categories": ["Billing", "Technical"] // ❌ No Fallback
233
+ }
234
+ }
235
+ \`\`\`
236
+
237
+ **Why Bad**: Missing Fallback category means unrecognized intents have no handler.
238
+
239
+ **Good Pattern**:
240
+ \`\`\`json
241
+ {
242
+ "categorizer": {
243
+ "categories": ["Billing", "Technical", "Fallback"] // ✅ Has Fallback
244
+ }
245
+ }
246
+ \`\`\`
247
+
248
+ **Why Good**: All intents have a handler.
249
+
250
+ ### Remediation
251
+
252
+ **How to Fix**:
253
+ 1. Add Fallback category to categorizer
254
+ 2. Ensure each category has a handler node
255
+ 3. Validate category hierarchy (TBD)
256
+
257
+ **Prevention** (During Generation):
258
+ - Always include Fallback category
259
+ - Ensure every category has at least one handler node
260
+
261
+ ---
262
+
263
+ ## Self-Check Checklist
264
+
265
+ After generating or modifying a workflow, verify:
266
+
267
+ - [ ] All paths produce all named results
268
+ - [ ] No multiple writers on same path
269
+ - [ ] Every path has response or abstain
270
+ - [ ] Category hierarchy is valid
271
+ - [ ] Categorizer has Fallback category
272
+ - [ ] All categories have handler nodes
273
+
274
+ ---
275
+
276
+ ## Reference
277
+
278
+ - **Source**: workflow-engine/pkg/engine/validator.go
279
+ - **Error Types**: RESULT_ERROR_TYPE_REQUIRED_OUTPUT_NOT_PRODUCED, RESULT_ERROR_TYPE_MULTIPLE_WRITERS, RESULT_ERROR_TYPE_MISSING_RESPONSE_OR_ABSTAIN_REASON, RESULT_ERROR_TYPE_CATEGORY_HIERARCHY_VIOLATION
280
+ - **Validation Tool**: Use \`workflow(mode="validate")\` to validate workflows
281
+ `;
282
+ }
283
+ /**
284
+ * Generate best practices checklist from SDK rules.
285
+ * SINGLE SOURCE OF TRUTH - all best practices come from:
286
+ * - ANTI_PATTERNS (validation-rules.ts)
287
+ * - STRUCTURAL_INVARIANTS (structural-rules.ts)
288
+ * - INPUT_SOURCE_RULES (validation-rules.ts)
289
+ * - OPTIMIZATION_RULES (validation-rules.ts)
290
+ */
291
+ export function generateBestPracticesChecklist() {
292
+ const criticalAntiPatterns = ANTI_PATTERNS.filter(ap => ap.severity === "critical");
293
+ const warningAntiPatterns = ANTI_PATTERNS.filter(ap => ap.severity === "warning");
294
+ const infoAntiPatterns = ANTI_PATTERNS.filter(ap => ap.severity === "info");
295
+ const criticalInvariants = STRUCTURAL_INVARIANTS.filter(inv => inv.severity === "critical");
296
+ const warningInvariants = STRUCTURAL_INVARIANTS.filter(inv => inv.severity === "warning");
297
+ return `# Workflow Best Practices Checklist
298
+
299
+ > **Auto-generated from SDK rules** - This is the single source of truth.
300
+ > Check this BEFORE deploying any workflow.
301
+
302
+ ## 🛑 CRITICAL (Must Fix Before Deploy)
303
+
304
+ ### Anti-Patterns to Avoid
305
+
306
+ ${criticalAntiPatterns.map(ap => `
307
+ #### ${ap.name}
308
+
309
+ **Pattern**: ${ap.pattern}
310
+
311
+ **Problem**: ${ap.problem}
312
+
313
+ **Solution**: ${ap.solution}
314
+
315
+ **Detection**: \`${ap.detection.condition}\`
316
+ `).join("\n---\n")}
317
+
318
+ ### Structural Requirements
319
+
320
+ ${criticalInvariants.map(inv => `
321
+ - **${inv.name}**: ${inv.rule}
322
+ - Violation: ${inv.violation}
323
+ - Fix: ${inv.fix}
324
+ `).join("")}
325
+
326
+ ---
327
+
328
+ ## ⚠️ WARNINGS (Should Fix)
329
+
330
+ ### Anti-Patterns
331
+
332
+ ${warningAntiPatterns.map(ap => `
333
+ - **${ap.name}**: ${ap.problem}
334
+ - Solution: ${ap.solution}
335
+ `).join("")}
336
+
337
+ ### Structural Warnings
338
+
339
+ ${warningInvariants.map(inv => `
340
+ - **${inv.name}**: ${inv.rule}
341
+ - Fix: ${inv.fix}
342
+ `).join("")}
343
+
344
+ ---
345
+
346
+ ## 📋 SUGGESTIONS (Good to Have)
347
+
348
+ ${infoAntiPatterns.map(ap => `
349
+ - **${ap.name}**: ${ap.problem}
350
+ - Recommendation: ${ap.solution}
351
+ `).join("")}
352
+
353
+ ---
354
+
355
+ ## Input Source Rules
356
+
357
+ | Action | Recommended Input | Avoid | Why |
358
+ |--------|-------------------|-------|-----|
359
+ ${INPUT_SOURCE_RULES.filter(r => r.severity === "critical").map(r => `| \`${r.actionPattern}\` | \`${r.recommended}\` | ${r.avoid.join(", ") || "-"} | ${r.reason.slice(0, 60)}... |`).join("\n")}
360
+
361
+ ---
362
+
363
+ ## Performance Optimizations
364
+
365
+ ${OPTIMIZATION_RULES.map(opt => `
366
+ ### ${opt.name} (${opt.priority})
367
+
368
+ - **Current State**: ${opt.currentState}
369
+ - **Recommendation**: ${opt.recommendation}
370
+ - **Benefit**: ${opt.benefit}
371
+ `).join("")}
372
+
373
+ ---
374
+
375
+ ## Quick Checklist
376
+
377
+ Before deploying, verify:
378
+
379
+ ### Critical
380
+ - [ ] Response nodes receive \`chat_conversation\` (prevents repeated questions)
381
+ - [ ] Email recipients from \`entity_extraction\` (not text outputs)
382
+ - [ ] Categorizers have Fallback category
383
+ - [ ] HITL has both success AND failure paths
384
+ - [ ] All paths reach WORKFLOW_OUTPUT
385
+ - [ ] No circular dependencies
386
+
387
+ ### Recommended
388
+ - [ ] \`max_tokens\` set on call_llm nodes
389
+ - [ ] No orphan nodes (unused/disconnected)
390
+ - [ ] No redundant search nodes
391
+ - [ ] Search nodes have data sources uploaded
392
+
393
+ ---
394
+
395
+ ## Related Resources
396
+
397
+ - \`ema://rules/anti-patterns\` - Full anti-pattern definitions (JSON)
398
+ - \`ema://rules/input-sources\` - Input source rules (JSON)
399
+ - \`ema://rules/extraction-column-format\` - extraction_columns API shape (entity_extraction nodes)
400
+ - \`ema://rules/json-output-patterns\` - custom_agent + json_mapper pattern
401
+ - \`ema://rules/chat-response-wiring\` - Chat response node wiring (avoid duplicate responses)
402
+ - \`ema://rules/email-input-wiring\` - Email input wiring (json_mapper/fixed_response patterns)
403
+ - \`ema://docs/workflow-node-reference\` - Per-node I/O types, categorizer patterns, LLM config
404
+ - \`ema://rules/structural-invariants\` - Structural rules (JSON)
405
+ - \`ema://rules/optimizations\` - Optimization rules (JSON)
406
+ - \`ema://validation/rules\` - Go validator rules reference
407
+ `;
408
+ }