@ema.co/mcp-toolkit 2026.3.25-1 → 2026.3.25-2
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/dist/knowledge/guidance-cache.js +41 -0
- package/dist/mcp/domain/generation-schema.js +228 -31
- package/dist/mcp/domain/workflow-def-validator.js +321 -1
- package/dist/mcp/guidance/classify.js +74 -0
- package/dist/mcp/guidance/defaults.js +114 -0
- package/dist/mcp/guidance/middleware.js +193 -0
- package/dist/mcp/guidance/types.js +7 -0
- package/dist/mcp/guidance.js +12 -8
- package/dist/mcp/handlers/data/index.js +1 -1
- package/dist/mcp/handlers/debug/index.js +80 -7
- package/dist/mcp/handlers/env/index.js +4 -4
- package/dist/mcp/handlers/feedback/index.js +15 -3
- package/dist/mcp/handlers/feedback/store.js +29 -11
- package/dist/mcp/handlers/template/crud.js +26 -9
- package/dist/mcp/handlers/workflow/deploy.js +15 -15
- package/dist/mcp/handlers/workflow/index.js +20 -8
- package/dist/mcp/handlers/workflow/validate.js +30 -1
- package/dist/mcp/handlers/workflow/validation.js +4 -4
- package/dist/mcp/server.js +53 -3
- package/dist/mcp/tools.js +11 -9
- package/package.json +1 -1
|
@@ -32,6 +32,7 @@ export class GuidanceCache {
|
|
|
32
32
|
patterns = [];
|
|
33
33
|
questions = [];
|
|
34
34
|
invariants = [];
|
|
35
|
+
contextualGuidanceMap = new Map();
|
|
35
36
|
warmedAt = null;
|
|
36
37
|
constructor(searchFn, config) {
|
|
37
38
|
this.searchFn = searchFn;
|
|
@@ -122,6 +123,32 @@ export class GuidanceCache {
|
|
|
122
123
|
this.maybeRefresh();
|
|
123
124
|
return this.invariants;
|
|
124
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Get contextual guidance for a specific key.
|
|
128
|
+
* Keys follow the pattern: "{tool}.{method}.{result_shape}"
|
|
129
|
+
* Also checks wildcards: "*.{method}.{shape}", "{tool}.*.{shape}", "*.*.{shape}"
|
|
130
|
+
*/
|
|
131
|
+
getContextualGuidance(tool, method, shape) {
|
|
132
|
+
this.maybeRefresh();
|
|
133
|
+
// Try exact match first, then progressively broader wildcards
|
|
134
|
+
const candidates = [
|
|
135
|
+
`${tool}.${method}.${shape}`,
|
|
136
|
+
`${tool}.*.${shape}`,
|
|
137
|
+
`*.${method}.${shape}`,
|
|
138
|
+
`*.*.${shape}`,
|
|
139
|
+
];
|
|
140
|
+
for (const key of candidates) {
|
|
141
|
+
const atom = this.contextualGuidanceMap.get(key);
|
|
142
|
+
if (atom)
|
|
143
|
+
return atom;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
/** All contextual guidance atoms (for debugging/inspection). */
|
|
148
|
+
getAllContextualGuidance() {
|
|
149
|
+
this.maybeRefresh();
|
|
150
|
+
return Array.from(this.contextualGuidanceMap.values());
|
|
151
|
+
}
|
|
125
152
|
// ── Internals ───────────────────────────────────────────────────────────
|
|
126
153
|
clear() {
|
|
127
154
|
this.rules = [];
|
|
@@ -131,6 +158,7 @@ export class GuidanceCache {
|
|
|
131
158
|
this.patterns = [];
|
|
132
159
|
this.questions = [];
|
|
133
160
|
this.invariants = [];
|
|
161
|
+
this.contextualGuidanceMap = new Map();
|
|
134
162
|
}
|
|
135
163
|
/**
|
|
136
164
|
* Index artifacts by reading structured data from structData.data.
|
|
@@ -184,6 +212,13 @@ export class GuidanceCache {
|
|
|
184
212
|
case "structural-invariant":
|
|
185
213
|
this.invariants.push(data);
|
|
186
214
|
break;
|
|
215
|
+
case "contextual-guidance": {
|
|
216
|
+
const atom = data;
|
|
217
|
+
const key = atom.key
|
|
218
|
+
?? `${atom.tool ?? "*"}.${atom.method ?? "*"}.${atom.result_shape ?? "*"}`;
|
|
219
|
+
this.contextualGuidanceMap.set(key, { ...atom, key });
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
187
222
|
default:
|
|
188
223
|
break;
|
|
189
224
|
}
|
|
@@ -209,10 +244,16 @@ export class GuidanceCache {
|
|
|
209
244
|
return "qualifying-question";
|
|
210
245
|
if (tags.includes("structural-invariant"))
|
|
211
246
|
return "structural-invariant";
|
|
247
|
+
if (tags.includes("structural-invariant"))
|
|
248
|
+
return "structural-invariant";
|
|
249
|
+
if (tags.includes("contextual-guidance"))
|
|
250
|
+
return "contextual-guidance";
|
|
212
251
|
if (name.includes("tool-guidance"))
|
|
213
252
|
return "tool-guidance";
|
|
214
253
|
if (name.startsWith("invariant-"))
|
|
215
254
|
return "structural-invariant";
|
|
255
|
+
if (name.includes("contextual-guidance"))
|
|
256
|
+
return "contextual-guidance";
|
|
216
257
|
return undefined;
|
|
217
258
|
}
|
|
218
259
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generation Schema
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* API-first schema generation: fetches live action definitions from the
|
|
5
|
+
* backend, enriches with catalog metadata, falls back to static catalog
|
|
6
|
+
* when the API is unavailable.
|
|
7
|
+
*
|
|
8
|
+
* This ensures agents always see the FULL set of required inputs/outputs
|
|
9
|
+
* as defined by the backend, not a hand-curated subset from documentation.
|
|
6
10
|
*
|
|
7
11
|
* The auto-builder sends ~70K tokens of documentation per request.
|
|
8
12
|
* This schema reduces that to ~5K tokens of actionable constraints.
|
|
@@ -11,42 +15,213 @@ import { AGENT_CATALOG } from "../knowledge.js";
|
|
|
11
15
|
import { INPUT_SOURCE_RULES } from "./validation-rules.js";
|
|
12
16
|
import { STRUCTURAL_INVARIANTS } from "./structural-rules.js";
|
|
13
17
|
import { LLM_ACTIONS, LLM_WIDGET_INPUTS, widgetBinding } from "../../config/widget-bindings.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//
|
|
18
|
+
import { searchDocuments } from "../../knowledge/search-client.js";
|
|
19
|
+
export async function buildCompactAgents(client) {
|
|
20
|
+
// Build catalog index for enrichment
|
|
21
|
+
const catalogIndex = new Map();
|
|
22
|
+
for (const agent of AGENT_CATALOG) {
|
|
23
|
+
catalogIndex.set(agent.actionName, agent);
|
|
24
|
+
}
|
|
25
|
+
// Try API-first for full input/output definitions
|
|
26
|
+
if (client) {
|
|
27
|
+
try {
|
|
28
|
+
const apiAgents = await buildFromApi(client, catalogIndex);
|
|
29
|
+
if (Object.keys(apiAgents).length > 0) {
|
|
30
|
+
return { agents: apiAgents, source: "api" };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
console.error(`[generation-schema] API listActions failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// DE middle layer: search for action definitions in Discovery Engine
|
|
38
|
+
try {
|
|
39
|
+
const deAgents = await buildFromDE(catalogIndex);
|
|
40
|
+
if (deAgents && Object.keys(deAgents).length > 0) {
|
|
41
|
+
return { agents: deAgents, source: "api" };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
console.error(`[generation-schema] DE search failed, using static catalog: ${e instanceof Error ? e.message : String(e)}`);
|
|
46
|
+
}
|
|
47
|
+
// Final fallback: static catalog (may have incomplete inputs)
|
|
48
|
+
return { agents: buildFromCatalog(catalogIndex), source: "catalog" };
|
|
49
|
+
}
|
|
50
|
+
/** Synchronous version for backwards compatibility (uses catalog only). */
|
|
51
|
+
export function buildCompactAgentsSync() {
|
|
52
|
+
const catalogIndex = new Map();
|
|
53
|
+
for (const agent of AGENT_CATALOG) {
|
|
54
|
+
catalogIndex.set(agent.actionName, agent);
|
|
55
|
+
}
|
|
56
|
+
return { agents: buildFromCatalog(catalogIndex), source: "catalog" };
|
|
57
|
+
}
|
|
17
58
|
/**
|
|
18
|
-
* Build
|
|
59
|
+
* Build agents from live API — full input/output definitions.
|
|
60
|
+
* Enriches with catalog metadata (description, whenToUse, etc.).
|
|
19
61
|
*/
|
|
20
|
-
|
|
62
|
+
async function buildFromApi(client, catalogIndex) {
|
|
63
|
+
const actionDTOs = await client.listActions();
|
|
21
64
|
const agents = {};
|
|
22
|
-
for (const
|
|
65
|
+
for (const dto of actionDTOs) {
|
|
66
|
+
const typeName = dto.typeName;
|
|
67
|
+
const name = typeName?.name?.name;
|
|
68
|
+
if (!name)
|
|
69
|
+
continue;
|
|
23
70
|
const inputs = {};
|
|
24
|
-
|
|
25
|
-
|
|
71
|
+
const outputs = {};
|
|
72
|
+
// Parse inputs from API response (FULL definition)
|
|
73
|
+
const inputsObj = dto.inputs?.inputs;
|
|
74
|
+
if (inputsObj && typeof inputsObj === "object") {
|
|
75
|
+
for (const [inputName, inputDef] of Object.entries(inputsObj)) {
|
|
76
|
+
const def = inputDef;
|
|
77
|
+
inputs[inputName] = {
|
|
78
|
+
type: (def.type?.wellKnownType ?? "WELL_KNOWN_TYPE_ANY"),
|
|
79
|
+
required: !def.isOptional,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
26
82
|
}
|
|
83
|
+
// Parse outputs from API response
|
|
84
|
+
const outputsObj = dto.outputs?.outputs;
|
|
85
|
+
if (outputsObj && typeof outputsObj === "object") {
|
|
86
|
+
for (const [outputName, outputDef] of Object.entries(outputsObj)) {
|
|
87
|
+
const def = outputDef;
|
|
88
|
+
outputs[outputName] = (def.type?.wellKnownType ?? "WELL_KNOWN_TYPE_ANY");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Inject widget inputs for LLM actions (may already be in API response)
|
|
92
|
+
if (LLM_ACTIONS.has(name)) {
|
|
93
|
+
for (const binding of LLM_WIDGET_INPUTS) {
|
|
94
|
+
if (!inputs[binding.inputName]) {
|
|
95
|
+
inputs[binding.inputName] = {
|
|
96
|
+
type: "WELL_KNOWN_TYPE_ANY",
|
|
97
|
+
required: binding.required,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Enrich with catalog metadata (doesn't override inputs/outputs)
|
|
103
|
+
const catalogEntry = catalogIndex.get(name);
|
|
104
|
+
agents[name] = {
|
|
105
|
+
name: catalogEntry?.displayName ?? name,
|
|
106
|
+
category: catalogEntry?.category ?? "unknown",
|
|
107
|
+
description: catalogEntry?.description,
|
|
108
|
+
whenToUse: catalogEntry?.whenToUse,
|
|
109
|
+
aliases: catalogEntry?.aliases,
|
|
110
|
+
tier: catalogEntry?.tier,
|
|
111
|
+
inputs,
|
|
112
|
+
outputs,
|
|
113
|
+
constraints: catalogEntry?.criticalRules,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return agents;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build agents from DE search — middle layer between API and static catalog.
|
|
120
|
+
* Queries DE for entity-type action docs, matches to catalog by action name
|
|
121
|
+
* extracted from the document ID (e.g., "entity:search_v2" → "search_v2").
|
|
122
|
+
* Returns null if DE search yields no action entities.
|
|
123
|
+
*/
|
|
124
|
+
async function buildFromDE(catalogIndex) {
|
|
125
|
+
const response = await searchDocuments("action definitions inputs outputs workflow actions", {
|
|
126
|
+
topK: 15,
|
|
127
|
+
filters: { type: ["entity"] },
|
|
128
|
+
});
|
|
129
|
+
if (!response.results || response.results.length === 0)
|
|
130
|
+
return null;
|
|
131
|
+
const deAgents = {};
|
|
132
|
+
const deActionNames = new Set();
|
|
133
|
+
for (const result of response.results) {
|
|
134
|
+
const doc = result.document;
|
|
135
|
+
if (!doc?.id)
|
|
136
|
+
continue;
|
|
137
|
+
// Extract action name from entity ID (e.g., "entity:search_v2" → "search_v2")
|
|
138
|
+
const actionName = doc.id.startsWith("entity:") ? doc.id.slice("entity:".length) : "";
|
|
139
|
+
if (!actionName)
|
|
140
|
+
continue;
|
|
141
|
+
deActionNames.add(actionName);
|
|
142
|
+
const catalogEntry = catalogIndex.get(actionName);
|
|
143
|
+
const inputs = {};
|
|
27
144
|
const outputs = {};
|
|
28
|
-
|
|
29
|
-
|
|
145
|
+
// Use catalog I/O specs (DE entities don't carry structured I/O)
|
|
146
|
+
if (catalogEntry) {
|
|
147
|
+
for (const input of catalogEntry.inputs ?? []) {
|
|
148
|
+
inputs[input.name] = { type: input.type, required: input.required };
|
|
149
|
+
}
|
|
150
|
+
for (const output of catalogEntry.outputs ?? []) {
|
|
151
|
+
outputs[output.name] = output.type;
|
|
152
|
+
}
|
|
30
153
|
}
|
|
31
|
-
if (LLM_ACTIONS.has(
|
|
154
|
+
if (LLM_ACTIONS.has(actionName)) {
|
|
32
155
|
for (const binding of LLM_WIDGET_INPUTS) {
|
|
156
|
+
if (!inputs[binding.inputName]) {
|
|
157
|
+
inputs[binding.inputName] = {
|
|
158
|
+
type: "WELL_KNOWN_TYPE_ANY",
|
|
159
|
+
required: binding.required,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
deAgents[actionName] = {
|
|
165
|
+
name: catalogEntry?.displayName ?? doc.name ?? actionName,
|
|
166
|
+
category: catalogEntry?.category ?? "unknown",
|
|
167
|
+
description: catalogEntry?.description ?? doc.description ?? doc.summary,
|
|
168
|
+
whenToUse: catalogEntry?.whenToUse,
|
|
169
|
+
aliases: catalogEntry?.aliases,
|
|
170
|
+
tier: catalogEntry?.tier,
|
|
171
|
+
inputs,
|
|
172
|
+
outputs,
|
|
173
|
+
constraints: catalogEntry?.criticalRules,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (deActionNames.size === 0)
|
|
177
|
+
return null;
|
|
178
|
+
// Supplement with catalog entries not found in DE
|
|
179
|
+
for (const [actionName, catalogEntry] of catalogIndex) {
|
|
180
|
+
if (deAgents[actionName])
|
|
181
|
+
continue;
|
|
182
|
+
deAgents[actionName] = buildCatalogAgent(actionName, catalogEntry);
|
|
183
|
+
}
|
|
184
|
+
return deAgents;
|
|
185
|
+
}
|
|
186
|
+
/** Build a CompactAgent from a single catalog entry. */
|
|
187
|
+
function buildCatalogAgent(actionName, entry) {
|
|
188
|
+
const inputs = {};
|
|
189
|
+
for (const input of entry.inputs ?? []) {
|
|
190
|
+
inputs[input.name] = { type: input.type, required: input.required };
|
|
191
|
+
}
|
|
192
|
+
const outputs = {};
|
|
193
|
+
for (const output of entry.outputs ?? []) {
|
|
194
|
+
outputs[output.name] = output.type;
|
|
195
|
+
}
|
|
196
|
+
if (LLM_ACTIONS.has(actionName)) {
|
|
197
|
+
for (const binding of LLM_WIDGET_INPUTS) {
|
|
198
|
+
if (!inputs[binding.inputName]) {
|
|
33
199
|
inputs[binding.inputName] = {
|
|
34
200
|
type: "WELL_KNOWN_TYPE_ANY",
|
|
35
201
|
required: binding.required,
|
|
36
202
|
};
|
|
37
203
|
}
|
|
38
204
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
name: entry.displayName,
|
|
208
|
+
category: entry.category,
|
|
209
|
+
description: entry.description,
|
|
210
|
+
whenToUse: entry.whenToUse,
|
|
211
|
+
aliases: entry.aliases,
|
|
212
|
+
tier: entry.tier,
|
|
213
|
+
inputs,
|
|
214
|
+
outputs,
|
|
215
|
+
constraints: entry.criticalRules,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Build agents from static AGENT_CATALOG (fallback when API unavailable).
|
|
220
|
+
*/
|
|
221
|
+
function buildFromCatalog(catalogIndex) {
|
|
222
|
+
const agents = {};
|
|
223
|
+
for (const [actionName, entry] of catalogIndex) {
|
|
224
|
+
agents[actionName] = buildCatalogAgent(actionName, entry);
|
|
50
225
|
}
|
|
51
226
|
return agents;
|
|
52
227
|
}
|
|
@@ -119,15 +294,37 @@ export function buildConstraints() {
|
|
|
119
294
|
};
|
|
120
295
|
}
|
|
121
296
|
/**
|
|
122
|
-
* Generate the complete compact schema for workflow generation
|
|
297
|
+
* Generate the complete compact schema for workflow generation.
|
|
298
|
+
* API-first: fetches live action definitions when client is provided.
|
|
123
299
|
*/
|
|
124
|
-
export function generateSchema(cache) {
|
|
125
|
-
|
|
300
|
+
export async function generateSchema(client, cache) {
|
|
301
|
+
const invariants = cache?.getInvariants()?.length
|
|
302
|
+
? cache.getInvariants()
|
|
303
|
+
: STRUCTURAL_INVARIANTS;
|
|
304
|
+
const { agents, source } = await buildCompactAgents(client);
|
|
305
|
+
return {
|
|
306
|
+
agents,
|
|
307
|
+
schema_source: source,
|
|
308
|
+
typeRules: buildTypeRules(),
|
|
309
|
+
inputRules: INPUT_SOURCE_RULES.map(rule => ({
|
|
310
|
+
action: rule.actionPattern,
|
|
311
|
+
recommended: rule.recommended,
|
|
312
|
+
avoid: rule.avoid,
|
|
313
|
+
severity: rule.severity,
|
|
314
|
+
})),
|
|
315
|
+
constraints: buildConstraints(),
|
|
316
|
+
structuralInvariants: invariants.map(({ id, rule, violation, fix, severity }) => ({ id, rule, violation, fix, severity })),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/** Synchronous version (catalog-only, no API). For callers that can't await. */
|
|
320
|
+
export function generateSchemaSync(cache) {
|
|
126
321
|
const invariants = cache?.getInvariants()?.length
|
|
127
322
|
? cache.getInvariants()
|
|
128
323
|
: STRUCTURAL_INVARIANTS;
|
|
324
|
+
const { agents, source } = buildCompactAgentsSync();
|
|
129
325
|
return {
|
|
130
|
-
agents
|
|
326
|
+
agents,
|
|
327
|
+
schema_source: source,
|
|
131
328
|
typeRules: buildTypeRules(),
|
|
132
329
|
inputRules: INPUT_SOURCE_RULES.map(rule => ({
|
|
133
330
|
action: rule.actionPattern,
|
|
@@ -144,7 +341,7 @@ export function generateSchema(cache) {
|
|
|
144
341
|
* This is ~5K tokens vs ~70K for full documentation
|
|
145
342
|
*/
|
|
146
343
|
export function generateSchemaMarkdown(cache) {
|
|
147
|
-
const schema =
|
|
344
|
+
const schema = generateSchemaSync(cache);
|
|
148
345
|
let md = `# Workflow Generation Schema\n\n`;
|
|
149
346
|
// Agent summary table with tier and description
|
|
150
347
|
md += `## Available Agents (${Object.keys(schema.agents).length})\n\n`;
|
|
@@ -217,7 +414,7 @@ export function generateSchemaMarkdown(cache) {
|
|
|
217
414
|
* Get compact agent info for a specific action
|
|
218
415
|
*/
|
|
219
416
|
export function getAgentSchema(actionName) {
|
|
220
|
-
const schema =
|
|
417
|
+
const schema = generateSchemaSync();
|
|
221
418
|
return schema.agents[actionName];
|
|
222
419
|
}
|
|
223
420
|
/**
|
|
@@ -231,7 +428,7 @@ export function isTypeCompatible(sourceType, targetType) {
|
|
|
231
428
|
if (sourceType === targetType)
|
|
232
429
|
return true;
|
|
233
430
|
// Check explicit rules
|
|
234
|
-
const schema =
|
|
431
|
+
const schema = generateSchemaSync();
|
|
235
432
|
const rule = schema.typeRules.find(r => r.sourceType === sourceType);
|
|
236
433
|
return rule?.targetTypes.includes(targetType) ?? false;
|
|
237
434
|
}
|