@ema.co/mcp-toolkit 1.5.2 → 1.6.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/dist/mcp/handlers-consolidated.js +251 -20
- package/dist/mcp/server.js +1 -203
- package/dist/mcp/tools-consolidated.js +146 -100
- package/dist/sdk/action-registry.js +128 -0
- package/dist/sdk/client.js +58 -90
- package/dist/sdk/generated/api-types.js +11 -0
- package/dist/sdk/index.js +15 -1
- package/dist/sdk/knowledge.js +38 -8
- package/dist/sdk/quality-gates.js +386 -0
- package/dist/sdk/structural-rules.js +290 -0
- package/dist/sdk/workflow-generator.js +88 -34
- package/dist/sdk/workflow-intent.js +237 -24
- package/dist/sdk/workflow-optimizer.js +665 -0
- package/dist/sdk/workflow-tracer.js +648 -0
- package/dist/sdk/workflow-transformer.js +10 -0
- package/dist/sdk/workflow-validator.js +391 -0
- package/docs/.temp/datasource-attach.har +198369 -0
- package/docs/.temp/grpcweb.gar +1 -0
- package/docs/local-generation.md +508 -0
- package/docs/mcp-flow-diagram.md +135 -0
- package/docs/mcp-tools-guide.md +157 -203
- package/docs/openapi.json +8000 -0
- package/docs/release-process.md +153 -0
- package/docs/tool-consolidation-proposal.md +166 -378
- package/package.json +3 -1
|
@@ -8,7 +8,9 @@ import { createVersionStorage } from "../sdk/version-storage.js";
|
|
|
8
8
|
import { createVersionPolicyEngine } from "../sdk/version-policy.js";
|
|
9
9
|
import { AGENT_CATALOG, WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, PLATFORM_CONCEPTS, WORKFLOW_EXECUTION_MODEL, COMMON_MISTAKES, DEBUG_CHECKLIST, GUIDANCE_TOPICS, VOICE_PERSONA_TEMPLATE, getAgentByName, getWidgetsForPersonaType, checkTypeCompatibility, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, getConceptByTerm, suggestAgentsForUseCase, validateWorkflowPrompt, detectWorkflowIssues, validateWorkflowConnections, suggestWorkflowFixes, } from "../sdk/knowledge.js";
|
|
10
10
|
import { compileWorkflow } from "../sdk/workflow-generator.js";
|
|
11
|
-
import {
|
|
11
|
+
import { ensureActionRegistry } from "../sdk/action-registry.js";
|
|
12
|
+
import { parseInput, intentToSpec, generateWorkflow } from "../sdk/workflow-intent.js";
|
|
13
|
+
import { ensureSchemaRegistry, validateWorkflowSpec, generateActionCatalogForLLM } from "../sdk/workflow-validator.js";
|
|
12
14
|
import { analyzeExecutionFlow, generateASCIIFlow } from "../sdk/workflow-execution-analyzer.js";
|
|
13
15
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
16
|
// Widget Validation Helpers
|
|
@@ -61,6 +63,29 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
|
|
|
61
63
|
const identifier = args.identifier; // deprecated alias
|
|
62
64
|
const idOrName = id ?? identifier;
|
|
63
65
|
const mode = args.mode;
|
|
66
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
67
|
+
// UNIFIED HANDLING: Route workflow operations to handleWorkflow
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
69
|
+
const input = args.input;
|
|
70
|
+
const optimize = args.optimize;
|
|
71
|
+
const workflowDef = args.workflow_def ?? args.workflow;
|
|
72
|
+
// If input is provided → this is a create/modify workflow operation
|
|
73
|
+
// If optimize is provided → this is an optimize operation
|
|
74
|
+
// If id + workflowDef provided → this is a deploy operation
|
|
75
|
+
// Route these to handleWorkflow
|
|
76
|
+
if (input || optimize || (idOrName && workflowDef)) {
|
|
77
|
+
// Map persona args to workflow args
|
|
78
|
+
const workflowArgs = {
|
|
79
|
+
...args,
|
|
80
|
+
persona_id: idOrName, // workflow uses persona_id not id
|
|
81
|
+
};
|
|
82
|
+
delete workflowArgs.id;
|
|
83
|
+
delete workflowArgs.identifier;
|
|
84
|
+
return handleWorkflow(workflowArgs, client, getTemplateId);
|
|
85
|
+
}
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
87
|
+
// Standard persona operations (get, list, compare, version management)
|
|
88
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
64
89
|
// Determine effective mode
|
|
65
90
|
let effectiveMode = mode;
|
|
66
91
|
if (!effectiveMode) {
|
|
@@ -752,6 +777,8 @@ export async function handleWorkflow(args, client, getTemplateId) {
|
|
|
752
777
|
if (!input) {
|
|
753
778
|
return { error: "input required for generate mode" };
|
|
754
779
|
}
|
|
780
|
+
// Load action registry for API-driven action versions/namespaces
|
|
781
|
+
const actionRegistry = await ensureActionRegistry(client);
|
|
755
782
|
const parseResult = parseInput(input);
|
|
756
783
|
if (!parseResult.validation.complete) {
|
|
757
784
|
return {
|
|
@@ -761,10 +788,75 @@ export async function handleWorkflow(args, client, getTemplateId) {
|
|
|
761
788
|
questions: parseResult.validation.questions,
|
|
762
789
|
};
|
|
763
790
|
}
|
|
764
|
-
//
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
791
|
+
// Override persona_type from args.type if provided (args.type takes precedence)
|
|
792
|
+
if (args.type) {
|
|
793
|
+
parseResult.intent.persona_type = args.type;
|
|
794
|
+
}
|
|
795
|
+
// Check if intent requires LLM-driven generation
|
|
796
|
+
const genResult = generateWorkflow(parseResult.intent);
|
|
797
|
+
// Load schema registry for API-driven validation (graceful degradation if unavailable)
|
|
798
|
+
let schemaRegistry;
|
|
799
|
+
try {
|
|
800
|
+
schemaRegistry = await ensureSchemaRegistry(client);
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
// Schema registry unavailable - skip API validation
|
|
804
|
+
schemaRegistry = null;
|
|
805
|
+
}
|
|
806
|
+
if (genResult.needs_llm) {
|
|
807
|
+
// Complex workflow - return prompt for LLM to generate
|
|
808
|
+
// Enhanced with API-driven action catalog if available
|
|
809
|
+
let enhancedPrompt = genResult.llm_prompt;
|
|
810
|
+
let availableActions = [];
|
|
811
|
+
let availableTemplates = [];
|
|
812
|
+
if (schemaRegistry) {
|
|
813
|
+
const actionCatalog = generateActionCatalogForLLM(schemaRegistry);
|
|
814
|
+
enhancedPrompt = genResult.llm_prompt ? {
|
|
815
|
+
system: genResult.llm_prompt.system + "\n\n" + actionCatalog,
|
|
816
|
+
user: genResult.llm_prompt.user,
|
|
817
|
+
} : undefined;
|
|
818
|
+
availableActions = schemaRegistry.getAllActions().map(a => a.name);
|
|
819
|
+
availableTemplates = schemaRegistry.getAllTemplates().map(t => ({ id: t.id, name: t.name, type: t.type }));
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
status: "needs_llm_generation",
|
|
823
|
+
reason: genResult.reason,
|
|
824
|
+
complexity: genResult.complexity,
|
|
825
|
+
llm_prompt: enhancedPrompt,
|
|
826
|
+
hint: "Send llm_prompt to an LLM, then call persona(workflow_def=<parsed_response>) to deploy.",
|
|
827
|
+
// Also provide basic spec as fallback (without complex chains)
|
|
828
|
+
fallback_spec: intentToSpec(parseResult.intent),
|
|
829
|
+
// Include catalogs for reference (if available)
|
|
830
|
+
available_actions: availableActions,
|
|
831
|
+
available_templates: availableTemplates,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
// Simple workflow - use generated spec directly
|
|
835
|
+
const spec = genResult.spec;
|
|
836
|
+
// Override spec name with provided name if given (args.name takes precedence over parsed name)
|
|
837
|
+
if (args.name) {
|
|
838
|
+
spec.name = args.name;
|
|
839
|
+
}
|
|
840
|
+
if (args.description) {
|
|
841
|
+
spec.description = args.description;
|
|
842
|
+
}
|
|
843
|
+
// Validate spec against API schemas BEFORE compiling (if schema registry available)
|
|
844
|
+
let specValidation = null;
|
|
845
|
+
if (schemaRegistry) {
|
|
846
|
+
specValidation = validateWorkflowSpec(spec, schemaRegistry);
|
|
847
|
+
if (!specValidation.valid) {
|
|
848
|
+
return {
|
|
849
|
+
status: "validation_failed",
|
|
850
|
+
errors: specValidation.errors,
|
|
851
|
+
warnings: specValidation.warnings,
|
|
852
|
+
action_coverage: specValidation.action_coverage,
|
|
853
|
+
hint: "Fix the validation errors and try again. Unknown actions may need to be checked against ListActions.",
|
|
854
|
+
spec_attempted: spec,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const compiled = compileWorkflow(spec, { registry: actionRegistry });
|
|
859
|
+
// Validate the generated workflow (structural checks)
|
|
768
860
|
const issues = detectWorkflowIssues(compiled.workflow_def);
|
|
769
861
|
const result = {
|
|
770
862
|
mode: "generate",
|
|
@@ -773,6 +865,14 @@ export async function handleWorkflow(args, client, getTemplateId) {
|
|
|
773
865
|
proto_config: compiled.proto_config,
|
|
774
866
|
validation: parseResult.validation,
|
|
775
867
|
};
|
|
868
|
+
// Include API validation results if available
|
|
869
|
+
if (specValidation) {
|
|
870
|
+
result.api_validation = {
|
|
871
|
+
valid: specValidation.valid,
|
|
872
|
+
warnings: specValidation.warnings,
|
|
873
|
+
action_coverage: specValidation.action_coverage,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
776
876
|
if (issues.length > 0) {
|
|
777
877
|
result.issues = issues;
|
|
778
878
|
}
|
|
@@ -790,18 +890,19 @@ export async function handleWorkflow(args, client, getTemplateId) {
|
|
|
790
890
|
result.deployed_to = { persona_id: personaId, persona_name: persona.name };
|
|
791
891
|
}
|
|
792
892
|
else if (!preview && !personaId) {
|
|
793
|
-
// GREENFIELD: Create new persona from template, then
|
|
794
|
-
// The template provides a valid workflow structure - we don't replace it,
|
|
795
|
-
// we just configure the persona settings (voice config, welcome message, etc.)
|
|
893
|
+
// GREENFIELD: Create new persona from template, then deploy workflow + proto_config
|
|
796
894
|
const personaName = args.name;
|
|
797
895
|
if (personaName) {
|
|
798
896
|
const personaType = args.type || "chat";
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
897
|
+
// Try API-driven template lookup first, fallback to hardcoded IDs
|
|
898
|
+
const FALLBACK_TEMPLATES = {
|
|
899
|
+
voice: "00000000-0000-0000-0000-00000000001e", // Voice AI template (fallback)
|
|
900
|
+
chat: "00000000-0000-0000-0000-000000000004", // Chat AI template (fallback)
|
|
901
|
+
dashboard: "00000000-0000-0000-0000-000000000002", // Dashboard AI template (fallback)
|
|
803
902
|
};
|
|
804
|
-
|
|
903
|
+
// Use registry to find template by type
|
|
904
|
+
const templateFromRegistry = actionRegistry.getTemplateForType(personaType);
|
|
905
|
+
const templateId = getTemplateId?.(personaType) || templateFromRegistry?.id || FALLBACK_TEMPLATES[personaType];
|
|
805
906
|
if (!templateId) {
|
|
806
907
|
return {
|
|
807
908
|
error: `No template found for type "${personaType}". Provide template_id or use type: voice, chat, or dashboard.`
|
|
@@ -859,12 +960,57 @@ export async function handleWorkflow(args, client, getTemplateId) {
|
|
|
859
960
|
...existingProtoConfig,
|
|
860
961
|
widgets: Array.from(widgetMap.values()),
|
|
861
962
|
};
|
|
862
|
-
// Step 4:
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
963
|
+
// Step 4: Deploy BOTH workflow AND proto_config for ALL persona types
|
|
964
|
+
// Voice AI IS driven by workflow (not just identityAndPurpose)
|
|
965
|
+
const existingWorkflow = newPersona.workflow_def;
|
|
966
|
+
const existingWfName = existingWorkflow?.workflowName;
|
|
967
|
+
// Clone our compiled workflow and set the namespace from the existing workflow
|
|
968
|
+
const workflowForDeploy = JSON.parse(JSON.stringify(compiled.workflow_def));
|
|
969
|
+
const wfName = workflowForDeploy.workflowName;
|
|
970
|
+
if (wfName?.name && existingWfName?.name) {
|
|
971
|
+
// Copy the exact namespace from the template workflow
|
|
972
|
+
wfName.name.namespaces = existingWfName.name.namespaces;
|
|
973
|
+
wfName.name.name = existingWfName.name.name; // template_id
|
|
974
|
+
}
|
|
975
|
+
// Fix results format - use "<actionName>.<outputName>" keys
|
|
976
|
+
const compiledResults = workflowForDeploy.results;
|
|
977
|
+
if (compiledResults) {
|
|
978
|
+
const newResults = {};
|
|
979
|
+
for (const [, value] of Object.entries(compiledResults)) {
|
|
980
|
+
if (value.actionName && value.outputName) {
|
|
981
|
+
const key = `${value.actionName}.${value.outputName}`;
|
|
982
|
+
newResults[key] = value;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
workflowForDeploy.results = newResults;
|
|
986
|
+
}
|
|
987
|
+
try {
|
|
988
|
+
await client.updateAiEmployee({
|
|
989
|
+
persona_id: newPersonaId,
|
|
990
|
+
workflow: workflowForDeploy,
|
|
991
|
+
proto_config: mergedProtoConfig,
|
|
992
|
+
}, { verbose: true });
|
|
993
|
+
}
|
|
994
|
+
catch (deployError) {
|
|
995
|
+
// Workflow deploy failed - still set proto_config
|
|
996
|
+
const errMsg = deployError instanceof Error ? deployError.message : String(deployError);
|
|
997
|
+
await client.updateAiEmployee({
|
|
998
|
+
persona_id: newPersonaId,
|
|
999
|
+
proto_config: mergedProtoConfig,
|
|
1000
|
+
});
|
|
1001
|
+
result.workflow_deploy_error = errMsg;
|
|
1002
|
+
result.workflow_attempted = workflowForDeploy;
|
|
1003
|
+
result.status = "partial";
|
|
1004
|
+
result.hint = "Persona created, config set, but workflow deploy failed. Check workflow_attempted for details.";
|
|
1005
|
+
result.deployed_to = {
|
|
1006
|
+
persona_id: newPersonaId,
|
|
1007
|
+
persona_name: personaName,
|
|
1008
|
+
created: true,
|
|
1009
|
+
workflow_deployed: false,
|
|
1010
|
+
};
|
|
1011
|
+
return result;
|
|
1012
|
+
}
|
|
1013
|
+
// Success - workflow deployed for all persona types
|
|
868
1014
|
result.deployed_to = {
|
|
869
1015
|
persona_id: newPersonaId,
|
|
870
1016
|
persona_name: personaName,
|
|
@@ -904,6 +1050,88 @@ export async function handleWorkflow(args, client, getTemplateId) {
|
|
|
904
1050
|
if (!persona) {
|
|
905
1051
|
return { error: `Persona not found: ${personaId}` };
|
|
906
1052
|
}
|
|
1053
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1054
|
+
// CONFIG-ONLY DETECTION: Route to proto_config update instead of workflow
|
|
1055
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1056
|
+
const lowerInput = input.toLowerCase();
|
|
1057
|
+
const isConfigOnlyUpdate = ((lowerInput.includes("voice settings") || lowerInput.includes("voice config")) ||
|
|
1058
|
+
(lowerInput.includes("welcome message") && !lowerInput.includes("add") && !lowerInput.includes("node")) ||
|
|
1059
|
+
(lowerInput.includes("identity") && lowerInput.includes("purpose")) ||
|
|
1060
|
+
(lowerInput.includes("speech characteristics")) ||
|
|
1061
|
+
(lowerInput.includes("hangup instructions") || lowerInput.includes("hangup")) ||
|
|
1062
|
+
(lowerInput.includes("update") && (lowerInput.includes("persona settings") ||
|
|
1063
|
+
lowerInput.includes("conversation settings") ||
|
|
1064
|
+
lowerInput.includes("proto_config") ||
|
|
1065
|
+
lowerInput.includes("chat settings"))));
|
|
1066
|
+
if (isConfigOnlyUpdate) {
|
|
1067
|
+
// This is a config update, not a workflow modification
|
|
1068
|
+
// Parse the config values from the input and update proto_config only
|
|
1069
|
+
const existingProtoConfig = (persona.proto_config ?? {});
|
|
1070
|
+
const existingWidgets = (existingProtoConfig.widgets ?? []);
|
|
1071
|
+
// Find and update the conversationSettings widget
|
|
1072
|
+
const widgetMap = new Map();
|
|
1073
|
+
for (const w of existingWidgets) {
|
|
1074
|
+
if (typeof w.name === "string" && w.name.trim().length > 0) {
|
|
1075
|
+
widgetMap.set(w.name, { ...w });
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
// Parse voice config updates from input
|
|
1079
|
+
const configUpdates = {};
|
|
1080
|
+
// Extract welcome message
|
|
1081
|
+
const welcomeMatch = input.match(/welcome\s*message[:\s]*["']?([^"'\n]+(?:\n(?![0-9]+\.).*)*?)(?:["']|(?=\n\n|\n[0-9]+\.))/i);
|
|
1082
|
+
if (welcomeMatch) {
|
|
1083
|
+
configUpdates.welcomeMessage = welcomeMatch[1].trim().replace(/^["']|["']$/g, '');
|
|
1084
|
+
}
|
|
1085
|
+
// Extract identity and purpose
|
|
1086
|
+
const identityMatch = input.match(/identity\s*(?:and\s*)?purpose[:\s]*["']?(.+?)(?:["']|(?=\n\n[0-9]+\.|$))/is);
|
|
1087
|
+
if (identityMatch) {
|
|
1088
|
+
configUpdates.identityAndPurpose = identityMatch[1].trim().replace(/^["']|["']$/g, '');
|
|
1089
|
+
}
|
|
1090
|
+
// Extract speech characteristics
|
|
1091
|
+
const speechMatch = input.match(/speech\s*characteristics?[:\s]*["']?([^"'\n]+)["']?/i);
|
|
1092
|
+
if (speechMatch) {
|
|
1093
|
+
configUpdates.speechCharacteristics = speechMatch[1].trim().replace(/^["']|["']$/g, '');
|
|
1094
|
+
}
|
|
1095
|
+
// Extract hangup instructions
|
|
1096
|
+
const hangupMatch = input.match(/hangup\s*instructions?[:\s]*["']?(.+?)(?:["']|$)/is);
|
|
1097
|
+
if (hangupMatch) {
|
|
1098
|
+
configUpdates.hangupInstructions = hangupMatch[1].trim().replace(/^["']|["']$/g, '');
|
|
1099
|
+
}
|
|
1100
|
+
// Update conversationSettings widget
|
|
1101
|
+
const convWidget = widgetMap.get("conversationSettings") || {
|
|
1102
|
+
name: "conversationSettings",
|
|
1103
|
+
type: 39,
|
|
1104
|
+
conversationSettings: {},
|
|
1105
|
+
};
|
|
1106
|
+
const convSettings = (convWidget.conversationSettings ?? {});
|
|
1107
|
+
// Merge config updates
|
|
1108
|
+
for (const [key, value] of Object.entries(configUpdates)) {
|
|
1109
|
+
convSettings[key] = value;
|
|
1110
|
+
}
|
|
1111
|
+
convWidget.conversationSettings = convSettings;
|
|
1112
|
+
widgetMap.set("conversationSettings", convWidget);
|
|
1113
|
+
const mergedProtoConfig = {
|
|
1114
|
+
...existingProtoConfig,
|
|
1115
|
+
widgets: Array.from(widgetMap.values()),
|
|
1116
|
+
};
|
|
1117
|
+
if (!preview) {
|
|
1118
|
+
// Deploy config update only - preserve existing workflow
|
|
1119
|
+
await client.updateAiEmployee({
|
|
1120
|
+
persona_id: personaId,
|
|
1121
|
+
proto_config: mergedProtoConfig,
|
|
1122
|
+
workflow: persona.workflow_def,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
mode: "config_update",
|
|
1127
|
+
status: preview ? "preview" : "deployed",
|
|
1128
|
+
persona_id: personaId,
|
|
1129
|
+
persona_name: persona.name,
|
|
1130
|
+
config_updates: configUpdates,
|
|
1131
|
+
note: "Updated persona configuration only. Workflow unchanged.",
|
|
1132
|
+
hint: "To modify the workflow itself, describe workflow changes like 'add search node' or 'add HITL before email'.",
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
907
1135
|
const existingWorkflow = persona.workflow_def;
|
|
908
1136
|
if (!existingWorkflow) {
|
|
909
1137
|
return {
|
|
@@ -1139,6 +1367,8 @@ export async function handleWorkflow(args, client, getTemplateId) {
|
|
|
1139
1367
|
if (!nodes) {
|
|
1140
1368
|
return { error: "nodes required for compile mode" };
|
|
1141
1369
|
}
|
|
1370
|
+
// Load action registry for API-driven action versions/namespaces
|
|
1371
|
+
const compileRegistry = await ensureActionRegistry(client);
|
|
1142
1372
|
const spec = {
|
|
1143
1373
|
name: args.name || "Compiled Workflow",
|
|
1144
1374
|
description: args.description || "Generated workflow",
|
|
@@ -1146,10 +1376,11 @@ export async function handleWorkflow(args, client, getTemplateId) {
|
|
|
1146
1376
|
nodes: nodes,
|
|
1147
1377
|
result_mappings: resultMappings || [],
|
|
1148
1378
|
};
|
|
1149
|
-
const compiled = compileWorkflow(spec);
|
|
1379
|
+
const compiled = compileWorkflow(spec, { registry: compileRegistry });
|
|
1150
1380
|
return {
|
|
1151
1381
|
workflow_def: compiled.workflow_def,
|
|
1152
1382
|
proto_config: compiled.proto_config,
|
|
1383
|
+
registry_loaded: compileRegistry.isLoaded(),
|
|
1153
1384
|
};
|
|
1154
1385
|
}
|
|
1155
1386
|
case "optimize": {
|
package/dist/mcp/server.js
CHANGED
|
@@ -2613,13 +2613,6 @@ const toolHandlers = {
|
|
|
2613
2613
|
preserveExistingNodes: false, // Replace with incoming structure
|
|
2614
2614
|
forceReplace: false, // Don't force if there are conflicts
|
|
2615
2615
|
});
|
|
2616
|
-
// #region agent log
|
|
2617
|
-
// Log brownfield merge result
|
|
2618
|
-
try {
|
|
2619
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:brownfieldMerge', message: 'Brownfield merge result', data: { success: mergeResult.success, description: mergeResult.description, diff: { nodesAdded: mergeResult.diff.nodesAdded, nodesRemoved: mergeResult.diff.nodesRemoved, nodesModified: mergeResult.diff.nodesModified, enumsAdded: mergeResult.diff.enumsAdded, enumsModified: mergeResult.diff.enumsModified, hasHitlNodes: mergeResult.diff.hasHitlNodes, conflictCount: mergeResult.diff.conflicts.length }, warnings: mergeResult.warnings, errors: mergeResult.errors, requiresAutobuilder: mergeResult.requiresAutobuilder }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'brownfield-merge', hypothesisId: 'H12' }) }).catch(() => { });
|
|
2620
|
-
}
|
|
2621
|
-
catch { }
|
|
2622
|
-
// #endregion agent log
|
|
2623
2616
|
if (!mergeResult.success) {
|
|
2624
2617
|
// Merge failed due to conflicts - return the errors
|
|
2625
2618
|
return {
|
|
@@ -2639,12 +2632,6 @@ const toolHandlers = {
|
|
|
2639
2632
|
// Validate the merged workflow structure
|
|
2640
2633
|
const mergeValidation = validateMergedWorkflow(workflowDef);
|
|
2641
2634
|
if (!mergeValidation.valid) {
|
|
2642
|
-
// #region agent log
|
|
2643
|
-
try {
|
|
2644
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:mergeValidationFailed', message: 'Merged workflow validation failed', data: { issues: mergeValidation.issues }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'brownfield-merge', hypothesisId: 'H12' }) }).catch(() => { });
|
|
2645
|
-
}
|
|
2646
|
-
catch { }
|
|
2647
|
-
// #endregion agent log
|
|
2648
2635
|
return {
|
|
2649
2636
|
environment: client["env"].name,
|
|
2650
2637
|
success: false,
|
|
@@ -2660,12 +2647,6 @@ const toolHandlers = {
|
|
|
2660
2647
|
// If HITL nodes are involved, force Autobuilder deployment
|
|
2661
2648
|
// (Direct API doesn't support HITL workflow changes)
|
|
2662
2649
|
if (mergeResult.requiresAutobuilder) {
|
|
2663
|
-
// #region agent log
|
|
2664
|
-
try {
|
|
2665
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:hitlRequiresAutobuilder', message: 'HITL workflow requires Autobuilder', data: { hasHitlNodes: mergeResult.diff.hasHitlNodes, nodesAdded: mergeResult.diff.nodesAdded }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'brownfield-merge', hypothesisId: 'H11' }) }).catch(() => { });
|
|
2666
|
-
}
|
|
2667
|
-
catch { }
|
|
2668
|
-
// #endregion agent log
|
|
2669
2650
|
deploymentMethod = "autobuilder";
|
|
2670
2651
|
}
|
|
2671
2652
|
}
|
|
@@ -2824,14 +2805,6 @@ const toolHandlers = {
|
|
|
2824
2805
|
actionOutputField.output = "hitl_status";
|
|
2825
2806
|
inlineRhsField.enumValue = correctedEnumVal;
|
|
2826
2807
|
hitlFixCount++;
|
|
2827
|
-
// #region agent log
|
|
2828
|
-
try {
|
|
2829
|
-
if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
|
|
2830
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:hitlAutoFix', message: 'Auto-fixed malformed HITL runIf', data: { nodeName: String(action.name), originalOutput: outputStr, correctedOutput: 'hitl_status', correctedEnumValue: correctedEnumVal }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'hitl-fix', hypothesisId: 'H1' }) }).catch(() => { });
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
|
-
catch { }
|
|
2834
|
-
// #endregion agent log
|
|
2835
2808
|
}
|
|
2836
2809
|
}
|
|
2837
2810
|
}
|
|
@@ -2842,57 +2815,6 @@ const toolHandlers = {
|
|
|
2842
2815
|
proto_config: mergedProtoConfig,
|
|
2843
2816
|
workflow: workflowDef,
|
|
2844
2817
|
};
|
|
2845
|
-
// #region agent log
|
|
2846
|
-
// H1/H2: confirm what we're attempting to deploy (structure only).
|
|
2847
|
-
try {
|
|
2848
|
-
if (process.env.EMA_MCP_DEBUG_LOGS !== "1") {
|
|
2849
|
-
// disabled
|
|
2850
|
-
}
|
|
2851
|
-
else {
|
|
2852
|
-
const wf = workflowDef ?? {};
|
|
2853
|
-
const actions = wf.actions ?? [];
|
|
2854
|
-
const actionNames = actions.map((a) => String(a.name ?? "")).filter(Boolean);
|
|
2855
|
-
const actionTypeNames = actions
|
|
2856
|
-
.map((a) => {
|
|
2857
|
-
const an = a.action;
|
|
2858
|
-
return String(an?.name?.name ?? "");
|
|
2859
|
-
})
|
|
2860
|
-
.filter(Boolean);
|
|
2861
|
-
const hasHitl = actionTypeNames.some((t) => t.toLowerCase().includes("hitl")) || actionNames.some((n) => n.toLowerCase().includes("hitl"));
|
|
2862
|
-
const hasTriggerWhen = actions.some((a) => {
|
|
2863
|
-
const inputs = a.inputs;
|
|
2864
|
-
return !!inputs && Object.prototype.hasOwnProperty.call(inputs, "trigger_when");
|
|
2865
|
-
});
|
|
2866
|
-
const runIfCount = actions.reduce((acc, a) => acc + (a.runIf ? 1 : 0), 0);
|
|
2867
|
-
const wfName = wf.workflowName ?? {};
|
|
2868
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:preDeploy', message: 'Deploy workflow request summary', data: { env: client["env"].name, personaId, validateFirst, autoFix, actionCount: actions.length, runIfCount, hasHitl, hasTriggerWhen, workflowName: wfName }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H1' }) }).catch(() => { });
|
|
2869
|
-
// H1: Detect malformed HITL runIf patterns
|
|
2870
|
-
const malformedHitlPatterns = [];
|
|
2871
|
-
for (const action of actions) {
|
|
2872
|
-
const runIf = action.runIf;
|
|
2873
|
-
if (!runIf)
|
|
2874
|
-
continue;
|
|
2875
|
-
const lhs = runIf.lhs;
|
|
2876
|
-
if (!lhs?.actionOutput)
|
|
2877
|
-
continue;
|
|
2878
|
-
const actionOutput = lhs.actionOutput;
|
|
2879
|
-
const output = String(actionOutput.output ?? "");
|
|
2880
|
-
// Malformed pattern: output contains both field name AND enum value like "hitl_status_HITL Success"
|
|
2881
|
-
if (output.includes("hitl_status_HITL") || output.includes("hitl_status HITL")) {
|
|
2882
|
-
malformedHitlPatterns.push({
|
|
2883
|
-
nodeName: String(action.name),
|
|
2884
|
-
output,
|
|
2885
|
-
issue: `Should be output="hitl_status" with enumValue="${output.replace(/hitl_status[_\s]?/i, "")}"`
|
|
2886
|
-
});
|
|
2887
|
-
}
|
|
2888
|
-
}
|
|
2889
|
-
if (malformedHitlPatterns.length > 0) {
|
|
2890
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:malformedHitl', message: 'DETECTED malformed HITL runIf patterns', data: { env: client["env"].name, personaId, malformedCount: malformedHitlPatterns.length, patterns: malformedHitlPatterns }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'debug-hitl', hypothesisId: 'H1' }) }).catch(() => { });
|
|
2891
|
-
}
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
catch { }
|
|
2895
|
-
// #endregion agent log
|
|
2896
2818
|
// Deployment attempt with automatic fallback
|
|
2897
2819
|
let deployedVia = "direct_api";
|
|
2898
2820
|
let autobuilderResult;
|
|
@@ -2902,50 +2824,6 @@ const toolHandlers = {
|
|
|
2902
2824
|
}
|
|
2903
2825
|
catch (err) {
|
|
2904
2826
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
2905
|
-
// #region agent log
|
|
2906
|
-
// H1/H2: capture top-level failure classification (do not log secrets).
|
|
2907
|
-
try {
|
|
2908
|
-
if (process.env.EMA_MCP_DEBUG_LOGS !== "1") {
|
|
2909
|
-
// disabled
|
|
2910
|
-
}
|
|
2911
|
-
else {
|
|
2912
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:directApiError', message: 'Direct deploy failed', data: { env: client["env"].name, personaId, errorMessage }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H2' }) }).catch(() => { });
|
|
2913
|
-
}
|
|
2914
|
-
}
|
|
2915
|
-
catch { }
|
|
2916
|
-
// #endregion agent log
|
|
2917
|
-
// H5: When update_persona returns a generic 500, attempt CheckWorkflow to extract structured validation errors.
|
|
2918
|
-
// This is best-effort and only runs when debug logs are enabled to minimize extra traffic.
|
|
2919
|
-
try {
|
|
2920
|
-
if (process.env.EMA_MCP_DEBUG_LOGS === "1" && workflowDef && errorMessage.toLowerCase().includes("internal server error")) {
|
|
2921
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2922
|
-
const check = await client.checkWorkflow(workflowDef);
|
|
2923
|
-
const checkJson = JSON.stringify(check);
|
|
2924
|
-
const snippet = checkJson.length > 3000 ? `${checkJson.slice(0, 3000)}…(truncated)` : checkJson;
|
|
2925
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:checkWorkflow', message: 'CheckWorkflow output (post-500)', data: { env: client["env"].name, personaId, checkSnippet: snippet }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H5' }) }).catch(() => { });
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
catch (checkErr) {
|
|
2929
|
-
try {
|
|
2930
|
-
if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
|
|
2931
|
-
const m = checkErr instanceof Error ? checkErr.message : String(checkErr);
|
|
2932
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:checkWorkflowError', message: 'CheckWorkflow failed (post-500)', data: { env: client["env"].name, personaId, error: m }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'pre-fix', hypothesisId: 'H5' }) }).catch(() => { });
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
2935
|
-
catch { }
|
|
2936
|
-
}
|
|
2937
|
-
// #region agent log
|
|
2938
|
-
// H2: Log fallback decision path for 500 errors
|
|
2939
|
-
try {
|
|
2940
|
-
if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
|
|
2941
|
-
const isNoExistingWorkflow = errorMessage.includes("Cannot set persona workflow without existing workflow");
|
|
2942
|
-
const isNameMismatch = errorMessage.includes("Workflow name does not match");
|
|
2943
|
-
const is500Error = errorMessage.toLowerCase().includes("internal server error") || errorMessage.includes("500");
|
|
2944
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:fallbackDecision', message: 'Evaluating fallback path', data: { env: client["env"].name, personaId, errorMessage, isNoExistingWorkflow, isNameMismatch, is500Error, hasWorkflowDef: !!workflowDef, willFallbackToAutobuilder: isNoExistingWorkflow || is500Error }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'debug-fallback', hypothesisId: 'H2' }) }).catch(() => { });
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
catch { }
|
|
2948
|
-
// #endregion agent log
|
|
2949
2827
|
// If direct API fails due to "no existing workflow", try Auto Builder
|
|
2950
2828
|
if (errorMessage.includes("Cannot set persona workflow without existing workflow") && workflowDef) {
|
|
2951
2829
|
deploymentMethod = "autobuilder";
|
|
@@ -2957,15 +2835,7 @@ const toolHandlers = {
|
|
|
2957
2835
|
`(Technical: ${errorMessage})`);
|
|
2958
2836
|
}
|
|
2959
2837
|
else if ((errorMessage.toLowerCase().includes("internal server error") || errorMessage.includes("500")) && workflowDef) {
|
|
2960
|
-
//
|
|
2961
|
-
// H2: 500 error - attempt Autobuilder fallback instead of throwing
|
|
2962
|
-
try {
|
|
2963
|
-
if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
|
|
2964
|
-
fetch('http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'src/mcp/server.ts:deploy_workflow:500Fallback', message: '500 error - attempting Autobuilder fallback', data: { env: client["env"].name, personaId, errorMessage }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'debug-fallback', hypothesisId: 'H2' }) }).catch(() => { });
|
|
2965
|
-
}
|
|
2966
|
-
}
|
|
2967
|
-
catch { }
|
|
2968
|
-
// #endregion agent log
|
|
2838
|
+
// 500 error - attempt Autobuilder fallback
|
|
2969
2839
|
deploymentMethod = "autobuilder";
|
|
2970
2840
|
}
|
|
2971
2841
|
else {
|
|
@@ -5408,34 +5278,6 @@ function generateEntityDocument(entityType, entity, related, tags) {
|
|
|
5408
5278
|
const promptRegistry = new PromptRegistry();
|
|
5409
5279
|
const resourceRegistry = new ResourceRegistry();
|
|
5410
5280
|
export async function startMcpServer() {
|
|
5411
|
-
// #region agent log
|
|
5412
|
-
// H6: confirm which server instance is running (cwd/version/env).
|
|
5413
|
-
try {
|
|
5414
|
-
if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
|
|
5415
|
-
fetch("http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22", {
|
|
5416
|
-
method: "POST",
|
|
5417
|
-
headers: { "Content-Type": "application/json" },
|
|
5418
|
-
body: JSON.stringify({
|
|
5419
|
-
location: "src/mcp/server.ts:startMcpServer:startup",
|
|
5420
|
-
message: "MCP server starting",
|
|
5421
|
-
data: {
|
|
5422
|
-
cwd: process.cwd(),
|
|
5423
|
-
argv0to5: process.argv.slice(0, 6),
|
|
5424
|
-
nodeEnv: process.env.NODE_ENV ?? null,
|
|
5425
|
-
emaEnvName: process.env.EMA_ENV_NAME ?? null,
|
|
5426
|
-
codeDir: process.env.EMA_MCP_CODE_DIR ?? null,
|
|
5427
|
-
debugLogs: process.env.EMA_MCP_DEBUG_LOGS ?? null,
|
|
5428
|
-
},
|
|
5429
|
-
timestamp: Date.now(),
|
|
5430
|
-
sessionId: "debug-session",
|
|
5431
|
-
runId: "pre-fix",
|
|
5432
|
-
hypothesisId: "H6",
|
|
5433
|
-
}),
|
|
5434
|
-
}).catch(() => { });
|
|
5435
|
-
}
|
|
5436
|
-
}
|
|
5437
|
-
catch { }
|
|
5438
|
-
// #endregion agent log
|
|
5439
5281
|
const server = new Server({ name: "ema", version: "1.0.0" }, {
|
|
5440
5282
|
capabilities: {
|
|
5441
5283
|
tools: {},
|
|
@@ -5450,28 +5292,6 @@ export async function startMcpServer() {
|
|
|
5450
5292
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
5451
5293
|
const { name, arguments: args } = request.params;
|
|
5452
5294
|
const handler = toolHandlers[name];
|
|
5453
|
-
// #region agent log
|
|
5454
|
-
// H6: prove which tool is invoked and with which top-level keys (no secrets).
|
|
5455
|
-
try {
|
|
5456
|
-
if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
|
|
5457
|
-
const argKeys = args && typeof args === "object" ? Object.keys(args) : [];
|
|
5458
|
-
fetch("http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22", {
|
|
5459
|
-
method: "POST",
|
|
5460
|
-
headers: { "Content-Type": "application/json" },
|
|
5461
|
-
body: JSON.stringify({
|
|
5462
|
-
location: "src/mcp/server.ts:CallToolRequestSchema:entry",
|
|
5463
|
-
message: "MCP tool invoked",
|
|
5464
|
-
data: { toolName: name, argKeys },
|
|
5465
|
-
timestamp: Date.now(),
|
|
5466
|
-
sessionId: "debug-session",
|
|
5467
|
-
runId: "pre-fix",
|
|
5468
|
-
hypothesisId: "H6",
|
|
5469
|
-
}),
|
|
5470
|
-
}).catch(() => { });
|
|
5471
|
-
}
|
|
5472
|
-
}
|
|
5473
|
-
catch { }
|
|
5474
|
-
// #endregion agent log
|
|
5475
5295
|
if (!handler) {
|
|
5476
5296
|
return {
|
|
5477
5297
|
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
|
|
@@ -5485,28 +5305,6 @@ export async function startMcpServer() {
|
|
|
5485
5305
|
};
|
|
5486
5306
|
}
|
|
5487
5307
|
catch (error) {
|
|
5488
|
-
// #region agent log
|
|
5489
|
-
// H6: capture top-level handler exception message.
|
|
5490
|
-
try {
|
|
5491
|
-
if (process.env.EMA_MCP_DEBUG_LOGS === "1") {
|
|
5492
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
5493
|
-
fetch("http://127.0.0.1:7245/ingest/c9b6768a-494d-4365-bd46-bf6c43dc1e22", {
|
|
5494
|
-
method: "POST",
|
|
5495
|
-
headers: { "Content-Type": "application/json" },
|
|
5496
|
-
body: JSON.stringify({
|
|
5497
|
-
location: "src/mcp/server.ts:CallToolRequestSchema:error",
|
|
5498
|
-
message: "Tool handler threw",
|
|
5499
|
-
data: { toolName: name, errorMessage: msg },
|
|
5500
|
-
timestamp: Date.now(),
|
|
5501
|
-
sessionId: "debug-session",
|
|
5502
|
-
runId: "pre-fix",
|
|
5503
|
-
hypothesisId: "H6",
|
|
5504
|
-
}),
|
|
5505
|
-
}).catch(() => { });
|
|
5506
|
-
}
|
|
5507
|
-
}
|
|
5508
|
-
catch { }
|
|
5509
|
-
// #endregion agent log
|
|
5510
5308
|
return {
|
|
5511
5309
|
content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }) }],
|
|
5512
5310
|
isError: true,
|