@ema.co/mcp-toolkit 2026.1.25 → 2026.1.26-3
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.
- package/README.md +10 -2
- package/dist/mcp/handlers/action/index.js +3 -18
- package/dist/mcp/handlers/data/index.js +385 -41
- package/dist/mcp/handlers/data/templates.js +107 -0
- package/dist/mcp/handlers/deprecation.js +50 -0
- package/dist/mcp/handlers/env/index.js +8 -4
- package/dist/mcp/handlers/knowledge/index.js +44 -237
- package/dist/mcp/handlers/persona/create.js +5 -11
- package/dist/mcp/handlers/persona/index.js +5 -1
- package/dist/mcp/handlers/persona/version.js +234 -0
- package/dist/mcp/handlers/sync/index.js +3 -18
- package/dist/mcp/handlers/template/index.js +75 -10
- package/dist/mcp/handlers/workflow/analyze.js +171 -0
- package/dist/mcp/handlers/workflow/compare.js +70 -0
- package/dist/mcp/handlers/workflow/compile.js +39 -0
- package/dist/mcp/handlers/workflow/deploy.js +73 -0
- package/dist/mcp/handlers/workflow/generate.js +350 -0
- package/dist/mcp/handlers/workflow/index.js +136 -0
- package/dist/mcp/handlers/workflow/modify.js +456 -0
- package/dist/mcp/handlers/workflow/optimize.js +136 -0
- package/dist/mcp/handlers/workflow/types.js +4 -0
- package/dist/mcp/handlers/workflow/utils.js +132 -0
- package/dist/mcp/handlers-consolidated.js +62 -2691
- package/dist/mcp/prompts.js +13 -14
- package/dist/mcp/resources.js +55 -54
- package/dist/mcp/server.js +93 -124
- package/dist/mcp/{tools-v2.js → tools.js} +1 -1
- package/dist/mcp/workflow-operations.js +2 -2
- package/dist/sdk/client-adapter.js +267 -32
- package/dist/sdk/client.js +31 -15
- package/dist/sdk/ema-client.js +183 -0
- package/dist/sdk/generated/template-fallbacks.js +123 -0
- package/dist/sdk/guidance.js +65 -11
- package/dist/sdk/index.js +3 -1
- package/dist/sdk/knowledge.js +16 -86
- package/dist/sdk/workflow-intent.js +27 -0
- package/dist/sdk/workflow-transformer.js +0 -342
- package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +481 -0
- package/docs/TODO-fix-analyzer-and-modify.md +182 -0
- package/package.json +9 -4
- package/dist/mcp/tools-consolidated.js +0 -875
- package/dist/mcp/tools-legacy.js +0 -736
- package/docs/CODEBASE-ANALYSIS-2026-01-23.md +0 -936
- package/docs/CODEBASE-ANALYSIS-PRIORITIZED.md +0 -774
- package/docs/api-contracts.md +0 -216
- package/docs/auto-builder-analysis.md +0 -271
- package/docs/blog/mcp-tool-design-lessons.md +0 -309
- package/docs/data-architecture.md +0 -166
- package/docs/demos/ap-invoice-generation.md +0 -347
- package/docs/demos/ap-invoice-processing.md +0 -271
- package/docs/ema-auto-builder-guide.html +0 -394
- package/docs/lessons-learned.md +0 -209
- package/docs/llm-native-workflow-design.md +0 -252
- package/docs/local-generation.md +0 -508
- package/docs/mcp-flow-diagram.md +0 -135
- package/docs/migration/action-composition-migration.md +0 -270
- package/docs/naming-conventions.md +0 -278
- package/docs/proposals/HANDOFF-tool-restructure.md +0 -526
- package/docs/proposals/action-composition.md +0 -490
- package/docs/proposals/explicit-method-restructure.md +0 -328
- package/docs/proposals/mcp-tool-restructure-2026-01.md +0 -366
- package/docs/proposals/self-contained-guidance.md +0 -427
- package/docs/proto-sdk-generation.md +0 -242
- package/docs/release-impact.md +0 -102
- package/docs/release-process.md +0 -157
- package/docs/staging.RULE.md +0 -142
- package/docs/test-persona-creation.md +0 -196
- package/docs/tool-consolidation-v2.md +0 -225
- package/docs/tool-response-standards.md +0 -256
- package/resources/demo-kits/README.md +0 -175
- package/resources/demo-kits/finance-ap/manifest.json +0 -150
- package/resources/demo-kits/tags.json +0 -91
- package/resources/docs/getting-started.md +0 -97
- package/resources/templates/auto-builder-rules.md +0 -224
- package/resources/templates/chat-ai/README.md +0 -119
- package/resources/templates/chat-ai/persona-config.json +0 -111
- package/resources/templates/dashboard-ai/README.md +0 -156
- package/resources/templates/dashboard-ai/persona-config.json +0 -180
- package/resources/templates/demo-scenarios/README.md +0 -63
- package/resources/templates/demo-scenarios/test-published-package.md +0 -116
- package/resources/templates/document-gen-ai/README.md +0 -132
- package/resources/templates/document-gen-ai/persona-config.json +0 -316
- package/resources/templates/voice-ai/README.md +0 -123
- package/resources/templates/voice-ai/persona-config.json +0 -74
- package/resources/templates/voice-ai/workflow-prompt.md +0 -121
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
# MCP Toolkit Debug Analysis: Unused Category & Type Mismatch Issues
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-01-25
|
|
4
|
+
**Context:** Debugging session for persona "EX - SKYE Self-Service AI Assistant"
|
|
5
|
+
**Persona ID:** `8ed79612-a8be-4c13-8736-daf5e4b0db96` (original), `2c5b7b2c-fee9-409f-8c87-d264fd4f27cd` (clone)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Executive Summary
|
|
10
|
+
|
|
11
|
+
During workflow analysis and modification of a two-stage categorizer workflow, we discovered:
|
|
12
|
+
|
|
13
|
+
1. **False Positive Bug**: The `unused_category` detection incorrectly attributes categories from one categorizer to another
|
|
14
|
+
2. **NLU Misinterpretation**: The `modify` handler interprets "rewire input X" as "remove nodes referencing X"
|
|
15
|
+
3. **Type Mismatch Guidance Gap**: No clear fix path for `TEXT_WITH_SOURCES` → `DOCUMENT` type mismatches
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Issue 1: Unused Category False Positive
|
|
20
|
+
|
|
21
|
+
### Symptom
|
|
22
|
+
|
|
23
|
+
The analyzer reports:
|
|
24
|
+
```
|
|
25
|
+
Category "Employment Verification" is defined in User Verification but has NO HANDLER
|
|
26
|
+
Category "Business Visa" is defined in User Verification but has NO HANDLER
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
But in reality:
|
|
30
|
+
- **User Verification** (`chat_categorizermys4rq`) only has: `Fallback`, `ID Verified`
|
|
31
|
+
- **Categorize Conversations** (`chat_categorizeraoh3e8`) has: `Fallback`, `Employment Verification`, `Business Visa`
|
|
32
|
+
|
|
33
|
+
### Root Cause
|
|
34
|
+
|
|
35
|
+
**File:** `src/sdk/knowledge.ts`, lines 2968-2976
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Find the enumType for this categorizer
|
|
39
|
+
const enumTypeName = categorizer.id + "_enumType";
|
|
40
|
+
const enumType = enumTypes.find(e => {
|
|
41
|
+
const name = e.name as Record<string, unknown> | undefined;
|
|
42
|
+
const innerName = name?.name as Record<string, unknown> | undefined;
|
|
43
|
+
const nameStr = String(innerName?.name ?? name?.name ?? "");
|
|
44
|
+
return nameStr.includes(categorizer.id) || nameStr.includes("enumType"); // BUG HERE
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**The Bug:**
|
|
49
|
+
- The condition `nameStr.includes("enumType")` is a fallback that matches ANY enumType
|
|
50
|
+
- When processing categorizer A, if the first condition fails, it grabs the FIRST enumType in the array (which might belong to categorizer B)
|
|
51
|
+
- This causes categories from enumType B to be attributed to categorizer A
|
|
52
|
+
|
|
53
|
+
**Evidence from workflow:**
|
|
54
|
+
```
|
|
55
|
+
enumTypes[0] = chat_categorizeraoh3e8_enumType_8vok (Employment Verification, Business Visa, Fallback)
|
|
56
|
+
enumTypes[1] = chat_categorizermys4rq_enumType_f4k7 (Fallback, ID Verified)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
When processing `chat_categorizermys4rq`, if the ID matching fails, the fallback `includes("enumType")` matches `enumTypes[0]` instead of `enumTypes[1]`.
|
|
60
|
+
|
|
61
|
+
### Fix Required
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// BEFORE (buggy)
|
|
65
|
+
const enumType = enumTypes.find(e => {
|
|
66
|
+
const nameStr = extractEnumTypeName(e);
|
|
67
|
+
return nameStr.includes(categorizer.id) || nameStr.includes("enumType"); // Too loose!
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// AFTER (correct)
|
|
71
|
+
const enumType = enumTypes.find(e => {
|
|
72
|
+
const nameStr = extractEnumTypeName(e);
|
|
73
|
+
// Only match if the enumType name contains this specific categorizer's ID
|
|
74
|
+
return nameStr.includes(categorizer.id);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// BETTER (robust) - Use the typeArguments reference
|
|
78
|
+
const categorizerAction = actions.find(a => a.name === categorizer.id);
|
|
79
|
+
const typeArgs = categorizerAction?.typeArguments as Record<string, unknown> | undefined;
|
|
80
|
+
const categoriesTypeArg = typeArgs?.categories as Record<string, unknown> | undefined;
|
|
81
|
+
const enumTypeRef = categoriesTypeArg?.enumType as Record<string, unknown> | undefined;
|
|
82
|
+
const enumTypeName = extractEnumTypeName({ name: enumTypeRef?.name });
|
|
83
|
+
|
|
84
|
+
const enumType = enumTypes.find(e => {
|
|
85
|
+
const name = extractEnumTypeName(e);
|
|
86
|
+
return name === enumTypeName;
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Test Case to Add
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// test/knowledge.test.ts
|
|
94
|
+
describe("detectUnusedCategories", () => {
|
|
95
|
+
it("should correctly associate enumTypes with their categorizers in multi-categorizer workflows", () => {
|
|
96
|
+
const workflow = {
|
|
97
|
+
actions: [
|
|
98
|
+
{ name: "chat_categorizermys4rq", /* User Verification */ },
|
|
99
|
+
{ name: "chat_categorizeraoh3e8", /* Document Router */ },
|
|
100
|
+
],
|
|
101
|
+
enumTypes: [
|
|
102
|
+
{
|
|
103
|
+
name: { name: { name: "chat_categorizeraoh3e8_enumType_8vok" } },
|
|
104
|
+
options: [
|
|
105
|
+
{ name: "Fallback" },
|
|
106
|
+
{ name: "Employment Verification" }, // Has handler
|
|
107
|
+
{ name: "Business Visa" }, // Has handler
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: { name: { name: "chat_categorizermys4rq_enumType_f4k7" } },
|
|
112
|
+
options: [
|
|
113
|
+
{ name: "Fallback" }, // Has handler
|
|
114
|
+
{ name: "ID Verified" }, // Has handler (triggers second categorizer)
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const issues = detectWorkflowIssues(workflow);
|
|
121
|
+
|
|
122
|
+
// Should NOT report unused_category for Employment Verification or Business Visa
|
|
123
|
+
// on chat_categorizermys4rq (User Verification)
|
|
124
|
+
const falsePositives = issues.filter(i =>
|
|
125
|
+
i.type === "unused_category" &&
|
|
126
|
+
i.node === "chat_categorizermys4rq" &&
|
|
127
|
+
(i.category === "Employment Verification" || i.category === "Business Visa")
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(falsePositives).toHaveLength(0);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Issue 2: NLU Misinterpretation in Modify Handler
|
|
138
|
+
|
|
139
|
+
### Symptom
|
|
140
|
+
|
|
141
|
+
Request: "rewire document_instructions input on EX - Hitachi Business Visa Letter Generation to use json_mapper5d37cx.output_json"
|
|
142
|
+
|
|
143
|
+
Result: Removes the entire `EX - Hitachi Business Visa Letter Generation` node instead of rewiring its input.
|
|
144
|
+
|
|
145
|
+
### Root Cause
|
|
146
|
+
|
|
147
|
+
**File:** `src/mcp/handlers/workflow/modify.ts`, lines 545-607
|
|
148
|
+
|
|
149
|
+
The `analyzeModificationRequest` function has aggressive node removal logic:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// REMOVAL
|
|
153
|
+
if (lowerInput.includes("remove") || lowerInput.includes("delete") ||
|
|
154
|
+
lowerInput.includes("clean up") || lowerInput.includes("orphan")) {
|
|
155
|
+
intent = "remove nodes";
|
|
156
|
+
|
|
157
|
+
// ... but then it also matches node names mentioned in ANY context:
|
|
158
|
+
if (operations.length === 0) {
|
|
159
|
+
const nodeNames = actions.map(a => String(a.name || "")).filter(n => n.length > 0);
|
|
160
|
+
const foundNodes = nodeNames.filter(nodeName =>
|
|
161
|
+
input.toLowerCase().includes(nodeName.toLowerCase()) // BUG: Too aggressive
|
|
162
|
+
);
|
|
163
|
+
if (foundNodes.length > 0) {
|
|
164
|
+
operations.push({
|
|
165
|
+
type: "remove",
|
|
166
|
+
description: `Remove node(s): ${foundNodes.join(", ")}`,
|
|
167
|
+
target_nodes: foundNodes,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The problem: When the input mentions node names in a "rewire" context, the removal detection still picks them up if no other operation is detected first.
|
|
175
|
+
|
|
176
|
+
### Fix Required
|
|
177
|
+
|
|
178
|
+
1. **Add explicit rewire detection before removal**:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// REWIRING (check BEFORE removal)
|
|
182
|
+
if (lowerInput.includes("rewire") ||
|
|
183
|
+
(lowerInput.includes("input") && lowerInput.includes("use")) ||
|
|
184
|
+
lowerInput.includes("change") && lowerInput.includes("to use")) {
|
|
185
|
+
intent = "rewire connections";
|
|
186
|
+
|
|
187
|
+
// Parse: "rewire X.input to use Y.output"
|
|
188
|
+
const rewireMatch = input.match(
|
|
189
|
+
/rewire\s+(?:the\s+)?(\w+)[\s.]+(\w+)\s+(?:input\s+)?(?:to\s+)?use\s+(\w+)\.(\w+)/i
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (rewireMatch) {
|
|
193
|
+
operations.push({
|
|
194
|
+
type: "rewire",
|
|
195
|
+
description: `Rewire ${rewireMatch[1]}.${rewireMatch[2]} to use ${rewireMatch[3]}.${rewireMatch[4]}`,
|
|
196
|
+
target: rewireMatch[1],
|
|
197
|
+
input_name: rewireMatch[2],
|
|
198
|
+
source: rewireMatch[3],
|
|
199
|
+
output_name: rewireMatch[4],
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
2. **Guard removal detection against rewire context**:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// Only do name-based removal if NOT in a rewire context
|
|
209
|
+
const isRewireContext = lowerInput.includes("rewire") ||
|
|
210
|
+
lowerInput.includes("to use") ||
|
|
211
|
+
lowerInput.includes("instead of");
|
|
212
|
+
|
|
213
|
+
if (!isRewireContext && operations.length === 0) {
|
|
214
|
+
// ... existing name-based removal logic
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
3. **Implement actual input rewiring in applyWorkflowModifications**:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
case "rewire": {
|
|
222
|
+
const targetNodeName = op.target;
|
|
223
|
+
const inputName = op.input_name;
|
|
224
|
+
const sourceName = op.source;
|
|
225
|
+
const outputName = op.output_name;
|
|
226
|
+
|
|
227
|
+
const targetNode = actions.find(a =>
|
|
228
|
+
String(a.name || "").toLowerCase().includes(targetNodeName.toLowerCase())
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
if (targetNode && targetNode.inputs) {
|
|
232
|
+
const inputs = targetNode.inputs as Record<string, unknown>;
|
|
233
|
+
|
|
234
|
+
// Find the input to rewire
|
|
235
|
+
const inputKey = Object.keys(inputs).find(k =>
|
|
236
|
+
k.toLowerCase().includes(inputName.toLowerCase())
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (inputKey) {
|
|
240
|
+
// Rewire the input
|
|
241
|
+
inputs[inputKey] = {
|
|
242
|
+
actionOutput: {
|
|
243
|
+
actionName: sourceName,
|
|
244
|
+
output: outputName,
|
|
245
|
+
},
|
|
246
|
+
autoDetectedBinding: false,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
nodesModified.push(String(targetNode.name));
|
|
250
|
+
connectionsChanged.push(`${targetNode.name}.${inputKey} ← ${sourceName}.${outputName}`);
|
|
251
|
+
changesApplied.push(`Rewired ${targetNode.name}.${inputKey} to use ${sourceName}.${outputName}`);
|
|
252
|
+
} else {
|
|
253
|
+
changesApplied.push(`Input "${inputName}" not found on "${targetNode.name}" - available: ${Object.keys(inputs).join(", ")}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Issue 3: Type Mismatch Guidance Gap
|
|
263
|
+
|
|
264
|
+
### Symptom
|
|
265
|
+
|
|
266
|
+
The analyzer correctly detects:
|
|
267
|
+
```
|
|
268
|
+
Type mismatch: WELL_KNOWN_TYPE_TEXT_WITH_SOURCES → WELL_KNOWN_TYPE_DOCUMENT
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
But provides no actionable fix:
|
|
272
|
+
```
|
|
273
|
+
# Either:
|
|
274
|
+
# 1. Use an intermediate conversion node
|
|
275
|
+
# 2. Connect to a named_inputs_* (accepts ANY type)
|
|
276
|
+
# 3. Use a different source output that matches the expected type
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Root Cause
|
|
280
|
+
|
|
281
|
+
**File:** `src/sdk/validation-rules.ts` - No rule for `TEXT_WITH_SOURCES` → `DOCUMENT` conversion
|
|
282
|
+
|
|
283
|
+
**File:** `src/sdk/knowledge.ts` - `suggestWorkflowFixes` returns generic guidance for type mismatches
|
|
284
|
+
|
|
285
|
+
### Fix Required
|
|
286
|
+
|
|
287
|
+
1. **Add type coercion guidance to validation-rules.ts**:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
export interface TypeCoercionRule {
|
|
291
|
+
sourceType: string;
|
|
292
|
+
targetType: string;
|
|
293
|
+
severity: "critical" | "warning" | "info";
|
|
294
|
+
workarounds: Array<{
|
|
295
|
+
method: string;
|
|
296
|
+
description: string;
|
|
297
|
+
example?: string;
|
|
298
|
+
}>;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export const TYPE_COERCION_RULES: TypeCoercionRule[] = [
|
|
302
|
+
{
|
|
303
|
+
sourceType: "WELL_KNOWN_TYPE_TEXT_WITH_SOURCES",
|
|
304
|
+
targetType: "WELL_KNOWN_TYPE_DOCUMENT",
|
|
305
|
+
severity: "warning", // Not critical - often works via runtime coercion
|
|
306
|
+
workarounds: [
|
|
307
|
+
{
|
|
308
|
+
method: "named_inputs",
|
|
309
|
+
description: "Pass via named_inputs which accepts any type",
|
|
310
|
+
example: `inputs: {
|
|
311
|
+
named_inputs: {
|
|
312
|
+
namedInputs: [{
|
|
313
|
+
name: "document_content",
|
|
314
|
+
binding: { actionOutput: { actionName: "source", output: "text_with_sources" } }
|
|
315
|
+
}]
|
|
316
|
+
}
|
|
317
|
+
}`
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
method: "call_llm_intermediary",
|
|
321
|
+
description: "Use call_llm to format content, then pass to document generator",
|
|
322
|
+
example: "call_llm (formats content) → generate_document"
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
method: "accept_runtime_coercion",
|
|
326
|
+
description: "Ema's runtime often handles type coercion automatically. Test before fixing.",
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
},
|
|
330
|
+
// ... other type coercion rules
|
|
331
|
+
];
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
2. **Improve suggestWorkflowFixes for type_mismatch**:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
case "type_mismatch": {
|
|
338
|
+
const coercionRule = TYPE_COERCION_RULES.find(r =>
|
|
339
|
+
issue.got?.includes(r.sourceType) && issue.expected?.includes(r.targetType)
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if (coercionRule) {
|
|
343
|
+
fix = {
|
|
344
|
+
issue_type: issue.type,
|
|
345
|
+
description: `Fix type mismatch: ${issue.source} → ${issue.target}`,
|
|
346
|
+
severity: coercionRule.severity,
|
|
347
|
+
workarounds: coercionRule.workarounds,
|
|
348
|
+
note: coercionRule.severity === "warning"
|
|
349
|
+
? "This may work at runtime due to automatic type coercion. Test before refactoring."
|
|
350
|
+
: undefined,
|
|
351
|
+
};
|
|
352
|
+
} else {
|
|
353
|
+
fix = { /* existing generic fix */ };
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Issue 4: Incomplete Input Source Rules for Persona Actions
|
|
362
|
+
|
|
363
|
+
### Symptom
|
|
364
|
+
|
|
365
|
+
When calling external AI Employees (persona actions) like `44ac6ec7-f46f-4f63-9f0f-b16a5ca349c3`, the analyzer doesn't understand their input requirements.
|
|
366
|
+
|
|
367
|
+
### Root Cause
|
|
368
|
+
|
|
369
|
+
**File:** `src/sdk/validation-rules.ts` - `INPUT_SOURCE_RULES` only covers internal action types, not persona actions.
|
|
370
|
+
|
|
371
|
+
### Fix Required
|
|
372
|
+
|
|
373
|
+
1. **Add dynamic input rule fetching for persona actions**:
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// In knowledge.ts detectWorkflowIssues
|
|
377
|
+
async function getInputRulesForPersonaAction(
|
|
378
|
+
personaId: string,
|
|
379
|
+
client: EmaClient
|
|
380
|
+
): Promise<InputSourceRule[]> {
|
|
381
|
+
// Fetch the persona's workflow to understand its inputs
|
|
382
|
+
const persona = await client.getPersonaById(personaId);
|
|
383
|
+
if (!persona?.workflow_def) return [];
|
|
384
|
+
|
|
385
|
+
// Extract workflow input schema
|
|
386
|
+
const workflowInputs = persona.workflow_def.workflowInputs;
|
|
387
|
+
|
|
388
|
+
// Generate rules based on input types
|
|
389
|
+
return Object.entries(workflowInputs).map(([inputName, inputDef]) => ({
|
|
390
|
+
actionPattern: personaId,
|
|
391
|
+
input: inputName,
|
|
392
|
+
expectedType: inputDef.type,
|
|
393
|
+
// ...
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
2. **Cache persona action schemas**:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
// src/sdk/persona-action-cache.ts
|
|
402
|
+
export class PersonaActionCache {
|
|
403
|
+
private cache = new Map<string, PersonaInputSchema>();
|
|
404
|
+
|
|
405
|
+
async getInputSchema(personaId: string, client: EmaClient): Promise<PersonaInputSchema | null> {
|
|
406
|
+
if (this.cache.has(personaId)) {
|
|
407
|
+
return this.cache.get(personaId)!;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Fetch and cache
|
|
411
|
+
const schema = await fetchPersonaInputSchema(personaId, client);
|
|
412
|
+
if (schema) {
|
|
413
|
+
this.cache.set(personaId, schema);
|
|
414
|
+
}
|
|
415
|
+
return schema;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Summary of Required Changes
|
|
423
|
+
|
|
424
|
+
### High Priority (Bug Fixes)
|
|
425
|
+
|
|
426
|
+
| File | Issue | Fix |
|
|
427
|
+
|------|-------|-----|
|
|
428
|
+
| `src/sdk/knowledge.ts:2968-2976` | enumType matching too loose | Use exact ID matching or typeArguments reference |
|
|
429
|
+
| `src/mcp/handlers/workflow/modify.ts:545-607` | Remove detection too aggressive | Add rewire context guard |
|
|
430
|
+
|
|
431
|
+
### Medium Priority (Feature Gaps)
|
|
432
|
+
|
|
433
|
+
| File | Issue | Fix |
|
|
434
|
+
|------|-------|-----|
|
|
435
|
+
| `src/sdk/validation-rules.ts` | No type coercion rules | Add `TYPE_COERCION_RULES` |
|
|
436
|
+
| `src/sdk/knowledge.ts` | Generic type mismatch fixes | Specific workarounds per type pair |
|
|
437
|
+
| `src/mcp/handlers/workflow/modify.ts` | No input rewiring implementation | Add `rewire` case to `applyWorkflowModifications` |
|
|
438
|
+
|
|
439
|
+
### Low Priority (Documentation/Knowledge)
|
|
440
|
+
|
|
441
|
+
| Area | Issue | Fix |
|
|
442
|
+
|------|-------|-----|
|
|
443
|
+
| `ema://rules/input-sources` | Missing persona action guidance | Document `named_inputs` pattern for persona actions |
|
|
444
|
+
| `ema://rules/anti-patterns` | No type mismatch pattern | Add `type-coercion-workarounds` pattern |
|
|
445
|
+
| Ema MCP Expert skill | Doesn't mention type coercion | Update skill with type mismatch guidance |
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Test Scenarios to Add
|
|
450
|
+
|
|
451
|
+
1. **Multi-categorizer enumType association** - Verify correct enumType ↔ categorizer pairing
|
|
452
|
+
2. **Rewire intent parsing** - Verify "rewire X to use Y" is not interpreted as "remove X and Y"
|
|
453
|
+
3. **Type mismatch severity** - Verify TEXT_WITH_SOURCES → DOCUMENT is warning, not critical
|
|
454
|
+
4. **Persona action input validation** - Verify external AIE inputs are validated correctly
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Reproduction Steps
|
|
459
|
+
|
|
460
|
+
To reproduce these issues:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# 1. Analyze the original persona
|
|
464
|
+
persona(method="analyze", id="8ed79612-a8be-4c13-8736-daf5e4b0db96", env="demo")
|
|
465
|
+
|
|
466
|
+
# 2. Observe false positive unused_category issues
|
|
467
|
+
|
|
468
|
+
# 3. Try to fix with modify
|
|
469
|
+
persona(method="update", id="...", input="rewire document_instructions to use json_mapper.output_json", preview=true)
|
|
470
|
+
|
|
471
|
+
# 4. Observe nodes being removed instead of rewired
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## References
|
|
477
|
+
|
|
478
|
+
- Original persona: `8ed79612-a8be-4c13-8736-daf5e4b0db96`
|
|
479
|
+
- Cloned persona: `2c5b7b2c-fee9-409f-8c87-d264fd4f27cd`
|
|
480
|
+
- Environment: demo
|
|
481
|
+
- MCP Toolkit version: 0.0.0-dev
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# TODO: Fix Analyzer and Modify Handler Issues
|
|
2
|
+
|
|
3
|
+
**Created:** 2026-01-25
|
|
4
|
+
**Status:** MOSTLY RESOLVED (2026-01-25)
|
|
5
|
+
**Context:** Debug session for SKYE Self-Service AI Assistant
|
|
6
|
+
|
|
7
|
+
> **UPDATE 2026-01-25**: The modify handler was completely refactored to remove
|
|
8
|
+
> ALL hardcoded NL parsing. See `.ctx/plans/2026-01-25-remove-hardcoded-logic.md`.
|
|
9
|
+
> Items 1-4 were addressed as part of the refactor (NL parsing removed entirely).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Critical Bugs (Fix Immediately)
|
|
14
|
+
|
|
15
|
+
### 1. [x] Fix enumType ↔ Categorizer Association Bug ✅ FIXED
|
|
16
|
+
|
|
17
|
+
**File:** `src/sdk/knowledge.ts`
|
|
18
|
+
**Line:** ~2968-2976
|
|
19
|
+
**Function:** `detectUnusedCategories`
|
|
20
|
+
|
|
21
|
+
**Problem:** The `enumTypes.find()` fallback `|| nameStr.includes("enumType")` matches ANY enumType, causing categories from categorizer B to be attributed to categorizer A.
|
|
22
|
+
|
|
23
|
+
**Fix:**
|
|
24
|
+
```typescript
|
|
25
|
+
// Replace this:
|
|
26
|
+
return nameStr.includes(categorizer.id) || nameStr.includes("enumType");
|
|
27
|
+
|
|
28
|
+
// With this:
|
|
29
|
+
return nameStr.includes(categorizer.id);
|
|
30
|
+
|
|
31
|
+
// Or better - use typeArguments reference:
|
|
32
|
+
const categorizerAction = actions.find(a => a.name === categorizer.id);
|
|
33
|
+
const enumTypeRef = categorizerAction?.typeArguments?.categories?.enumType?.name?.name?.name;
|
|
34
|
+
const enumType = enumTypes.find(e => extractEnumTypeName(e) === enumTypeRef);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Test:** Add test for multi-categorizer workflows with distinct enumTypes.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### 2. [x] Fix Modify Handler Rewire vs Remove Confusion ✅ RESOLVED
|
|
42
|
+
|
|
43
|
+
**Status:** RESOLVED via architecture change.
|
|
44
|
+
|
|
45
|
+
**Resolution:** The entire NL-parsing approach was removed. The modify handler
|
|
46
|
+
now accepts STRUCTURED OPERATIONS from the Agent, not natural language.
|
|
47
|
+
|
|
48
|
+
The Agent (LLM) builds operations like:
|
|
49
|
+
```typescript
|
|
50
|
+
{
|
|
51
|
+
type: "rewire",
|
|
52
|
+
rewire: {
|
|
53
|
+
target_node: "downstream_node",
|
|
54
|
+
target_input: "query",
|
|
55
|
+
source_node: "upstream_node",
|
|
56
|
+
source_output: "response",
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
No regex parsing needed - the LLM naturally understands user intent.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Medium Priority (Feature Gaps)
|
|
66
|
+
|
|
67
|
+
### 3. [ ] Add Type Coercion Rules and Guidance
|
|
68
|
+
|
|
69
|
+
**File:** `src/sdk/validation-rules.ts`
|
|
70
|
+
|
|
71
|
+
**Status:** Still applicable. Consider adding for validation feedback.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### 4. [x] Implement Rewire Operation in applyWorkflowModifications ✅ DONE
|
|
76
|
+
|
|
77
|
+
**Status:** IMPLEMENTED in `src/mcp/handlers/workflow/modify.ts`
|
|
78
|
+
|
|
79
|
+
The `executeRewire()` function handles structured rewire operations:
|
|
80
|
+
```typescript
|
|
81
|
+
const result = await handleWorkflowModify(args, client);
|
|
82
|
+
// With operations: [{ type: "rewire", rewire: { target_node, target_input, source_node, source_output } }]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### 5. [ ] Improve Type Mismatch Fix Suggestions
|
|
88
|
+
|
|
89
|
+
**File:** `src/sdk/knowledge.ts`
|
|
90
|
+
**Function:** `suggestWorkflowFixes`
|
|
91
|
+
|
|
92
|
+
**Update type_mismatch case to use TYPE_COERCION_RULES:**
|
|
93
|
+
```typescript
|
|
94
|
+
case "type_mismatch": {
|
|
95
|
+
const rule = TYPE_COERCION_RULES.find(r =>
|
|
96
|
+
issue.got?.includes(r.sourceType) && issue.expected?.includes(r.targetType)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
fix = rule ? {
|
|
100
|
+
issue_type: issue.type,
|
|
101
|
+
severity: rule.severity,
|
|
102
|
+
workarounds: rule.workarounds,
|
|
103
|
+
note: rule.severity === "warning" ? "May work at runtime - test first" : undefined,
|
|
104
|
+
} : { /* existing generic fix */ };
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Low Priority (Documentation)
|
|
112
|
+
|
|
113
|
+
### 6. [ ] Update ema://rules/anti-patterns Resource
|
|
114
|
+
|
|
115
|
+
**File:** `src/mcp/resources.ts` or generated resource
|
|
116
|
+
|
|
117
|
+
**Add pattern:**
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"id": "type-mismatch-persona-action",
|
|
121
|
+
"name": "Type Mismatch with Persona Actions",
|
|
122
|
+
"pattern": "Connecting TEXT_WITH_SOURCES output to persona action's DOCUMENT input",
|
|
123
|
+
"problem": "Persona actions have specific input type requirements",
|
|
124
|
+
"solution": "Use named_inputs which accepts any type, or test runtime coercion",
|
|
125
|
+
"severity": "warning"
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 7. [ ] Update Ema MCP Expert Skill
|
|
132
|
+
|
|
133
|
+
**File:** `skills/ema-mcp-expert/SKILL.md` (generated)
|
|
134
|
+
|
|
135
|
+
**Add troubleshooting entry:**
|
|
136
|
+
```markdown
|
|
137
|
+
| Symptom | Likely Cause | Fix |
|
|
138
|
+
|---------|--------------|-----|
|
|
139
|
+
| Type mismatch with persona action | Persona expects different type | Use named_inputs or test runtime |
|
|
140
|
+
| Unused category false positive | Multi-categorizer enumType bug | Verify actual enumType associations |
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### 8. [ ] Add Input Source Rule for Persona Actions
|
|
146
|
+
|
|
147
|
+
**File:** `src/sdk/validation-rules.ts`
|
|
148
|
+
|
|
149
|
+
**Add guidance:**
|
|
150
|
+
```typescript
|
|
151
|
+
{
|
|
152
|
+
actionPattern: "persona_action", // Generic pattern for all persona actions
|
|
153
|
+
recommended: "named_inputs for flexible type handling",
|
|
154
|
+
avoid: ["direct document_instructions without type validation"],
|
|
155
|
+
reason: "Persona actions have specific workflow input schemas. Use named_inputs for flexibility.",
|
|
156
|
+
severity: "info",
|
|
157
|
+
fix: "Pass data via named_inputs: { namedInputs: [{ name: '...', binding: {...} }] }",
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Testing Checklist
|
|
164
|
+
|
|
165
|
+
- [ ] Unit test: Multi-categorizer enumType association
|
|
166
|
+
- [ ] Unit test: Rewire intent parsing (should NOT trigger removal)
|
|
167
|
+
- [ ] Unit test: Type mismatch severity levels
|
|
168
|
+
- [ ] Integration test: Clone + modify workflow without false positives
|
|
169
|
+
- [ ] E2E test: SKYE workflow analysis returns 0 critical false positives
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Files Modified
|
|
174
|
+
|
|
175
|
+
| File | Changes |
|
|
176
|
+
|------|---------|
|
|
177
|
+
| `src/sdk/knowledge.ts` | Fix enumType matching, improve type mismatch fixes |
|
|
178
|
+
| `src/sdk/validation-rules.ts` | Add TYPE_COERCION_RULES |
|
|
179
|
+
| `src/mcp/handlers/workflow/modify.ts` | Add rewire detection and implementation |
|
|
180
|
+
| `src/mcp/resources.ts` | Update anti-patterns resource |
|
|
181
|
+
| `test/knowledge.test.ts` | Add multi-categorizer tests |
|
|
182
|
+
| `test/handlers/workflow/modify.test.ts` | Add rewire tests |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ema.co/mcp-toolkit",
|
|
3
|
-
"version": "2026.1.
|
|
3
|
+
"version": "2026.1.26-3",
|
|
4
4
|
"description": "Ema AI Employee toolkit - MCP server, CLI, and SDK for managing AI Employees across environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,18 +52,23 @@
|
|
|
52
52
|
"lint:unused": "knip",
|
|
53
53
|
"precommit": "npm run typecheck && npm run test",
|
|
54
54
|
"prepare": "husky",
|
|
55
|
-
"
|
|
55
|
+
"check:publish": "tsx scripts/check-publish-content.ts",
|
|
56
|
+
"prepublishOnly": "npm run build && npm run test && npm run check:publish",
|
|
56
57
|
"generate:types": "tsx scripts/generate-types.ts",
|
|
57
58
|
"generate:api-client": "npx @hey-api/openapi-ts -i src/sdk/generated/openapi.json -o src/sdk/generated/api-client -c @hey-api/client-fetch",
|
|
58
59
|
"generate:protos": "buf generate ../ema-repos/protos --template buf.gen.yaml",
|
|
59
60
|
"generate:proto-fields": "tsx scripts/generate-proto-fields.ts",
|
|
60
61
|
"generate:action-schema": "tsx scripts/generate-action-schema.ts",
|
|
61
|
-
"generate:
|
|
62
|
+
"generate:skill": "tsx scripts/generate-skill.ts",
|
|
63
|
+
"generate:skill:check": "tsx scripts/generate-skill.ts --check",
|
|
64
|
+
"generate:templates": "tsx scripts/generate-templates.ts",
|
|
65
|
+
"generate:templates:check": "tsx scripts/generate-templates.ts --check",
|
|
66
|
+
"generate:all": "npm run generate:protos && npm run generate:api-client && npm run generate:templates && npm run generate:skill",
|
|
62
67
|
"check:contracts": "tsx scripts/check-api-contracts.ts",
|
|
63
68
|
"check:schema-sync": "tsx scripts/check-schema-handler-sync.ts",
|
|
64
69
|
"check:prompt-refs": "tsx scripts/check-prompt-refs.ts",
|
|
65
70
|
"check:action-aliases": "tsx scripts/check-action-aliases.ts",
|
|
66
|
-
"check:all": "npm run check:contracts && npm run check:schema-sync && npm run check:prompt-refs && npm run check:action-aliases",
|
|
71
|
+
"check:all": "npm run check:contracts && npm run check:schema-sync && npm run check:prompt-refs && npm run check:action-aliases && npm run generate:skill:check",
|
|
67
72
|
"release:check": "tsx scripts/check-release-need.ts"
|
|
68
73
|
},
|
|
69
74
|
"repository": {
|