@bridge_gpt/mcp-server 0.1.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/LICENSE +21 -0
- package/README.md +178 -0
- package/build/index.js +1630 -0
- package/build/pipeline-utils.js +175 -0
- package/build/pipelines.generated.js +722 -0
- package/package.json +47 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline utilities — pure functions for schema validation, variable
|
|
3
|
+
* substitution, and recipe resolution. Consumed by the build-time bundler
|
|
4
|
+
* (bundle-pipelines.js) and by MCP tool handlers at runtime.
|
|
5
|
+
*/
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Schema Validation
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
export function validatePipelineSchema(json) {
|
|
10
|
+
const errors = [];
|
|
11
|
+
if (typeof json !== "object" || json === null || Array.isArray(json)) {
|
|
12
|
+
return { valid: false, errors: ["Pipeline must be a JSON object."] };
|
|
13
|
+
}
|
|
14
|
+
const obj = json;
|
|
15
|
+
// Top-level required fields
|
|
16
|
+
if (typeof obj.name !== "string" || obj.name.trim() === "") {
|
|
17
|
+
errors.push('Missing or empty required field "name" (string).');
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(obj.steps) || obj.steps.length === 0) {
|
|
20
|
+
errors.push('Missing or empty required field "steps" (non-empty array).');
|
|
21
|
+
return { valid: false, errors };
|
|
22
|
+
}
|
|
23
|
+
// Optional fields type checks
|
|
24
|
+
if (obj.description !== undefined && typeof obj.description !== "string") {
|
|
25
|
+
errors.push('"description" must be a string if provided.');
|
|
26
|
+
}
|
|
27
|
+
if (obj.variables !== undefined) {
|
|
28
|
+
if (!Array.isArray(obj.variables) ||
|
|
29
|
+
!obj.variables.every((v) => typeof v === "string")) {
|
|
30
|
+
errors.push('"variables" must be an array of strings if provided.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Step validation
|
|
34
|
+
const steps = obj.steps;
|
|
35
|
+
for (let i = 0; i < steps.length; i++) {
|
|
36
|
+
const prefix = `steps[${i}]`;
|
|
37
|
+
const step = steps[i];
|
|
38
|
+
if (typeof step !== "object" || step === null || Array.isArray(step)) {
|
|
39
|
+
errors.push(`${prefix}: must be an object.`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const s = step;
|
|
43
|
+
if (typeof s.description !== "string" || s.description.trim() === "") {
|
|
44
|
+
errors.push(`${prefix}: missing or empty "description" (string).`);
|
|
45
|
+
}
|
|
46
|
+
if (s.on_error !== undefined) {
|
|
47
|
+
if (s.on_error !== "halt" && s.on_error !== "warn_and_continue") {
|
|
48
|
+
errors.push(`${prefix}: "on_error" must be "halt" or "warn_and_continue".`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (s.requires_approval !== undefined && typeof s.requires_approval !== "boolean") {
|
|
52
|
+
errors.push(`${prefix}: "requires_approval" must be a boolean if provided.`);
|
|
53
|
+
}
|
|
54
|
+
if (s.type === "mcp_call") {
|
|
55
|
+
if (typeof s.tool !== "string" || s.tool.trim() === "") {
|
|
56
|
+
errors.push(`${prefix}: mcp_call step requires "tool" (string).`);
|
|
57
|
+
}
|
|
58
|
+
if (typeof s.params !== "object" || s.params === null || Array.isArray(s.params)) {
|
|
59
|
+
errors.push(`${prefix}: mcp_call step requires "params" (object).`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (s.type === "agent_task") {
|
|
63
|
+
const hasInstruction = typeof s.instruction === "string" && s.instruction.trim() !== "";
|
|
64
|
+
const hasInstructionFile = typeof s.instruction_file === "string" &&
|
|
65
|
+
s.instruction_file.trim() !== "";
|
|
66
|
+
if (hasInstruction && hasInstructionFile) {
|
|
67
|
+
errors.push(`${prefix}: agent_task must have exactly one of "instruction" or "instruction_file", not both.`);
|
|
68
|
+
}
|
|
69
|
+
else if (!hasInstruction && !hasInstructionFile) {
|
|
70
|
+
errors.push(`${prefix}: agent_task must have exactly one of "instruction" or "instruction_file".`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
errors.push(`${prefix}: "type" must be "mcp_call" or "agent_task", got "${String(s.type)}".`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { valid: errors.length === 0, errors };
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Variable Handling
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
const variablePattern = () => /\{([a-zA-Z_][a-zA-Z0-9_]*)}/g;
|
|
83
|
+
export function extractVariableReferences(text) {
|
|
84
|
+
const refs = [];
|
|
85
|
+
let match;
|
|
86
|
+
const pattern = variablePattern();
|
|
87
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
88
|
+
if (!refs.includes(match[1])) {
|
|
89
|
+
refs.push(match[1]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return refs;
|
|
93
|
+
}
|
|
94
|
+
export function substituteVariables(template, variables) {
|
|
95
|
+
return template.replace(variablePattern(), (full, name) => {
|
|
96
|
+
if (name in variables) {
|
|
97
|
+
return variables[name];
|
|
98
|
+
}
|
|
99
|
+
return full; // leave unresolved tokens as-is (build validates completeness)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function substituteDeep(value, variables) {
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
return substituteVariables(value, variables);
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
return value.map((item) => substituteDeep(item, variables));
|
|
108
|
+
}
|
|
109
|
+
if (typeof value === "object" && value !== null) {
|
|
110
|
+
const result = {};
|
|
111
|
+
for (const [k, v] of Object.entries(value)) {
|
|
112
|
+
result[k] = substituteDeep(v, variables);
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
function substituteInParams(params, variables) {
|
|
119
|
+
return substituteDeep(params, variables);
|
|
120
|
+
}
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Recipe Resolution
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
export function resolveRecipe(pipeline, instructions, variables, skipSteps) {
|
|
125
|
+
// Validate required variables are provided
|
|
126
|
+
const declared = pipeline.variables ?? [];
|
|
127
|
+
const missing = declared.filter((v) => !(v in variables));
|
|
128
|
+
if (missing.length > 0) {
|
|
129
|
+
throw new Error(`Missing required variable(s): ${missing.join(", ")}. ` +
|
|
130
|
+
`Pipeline "${pipeline.name}" declares: [${declared.join(", ")}].`);
|
|
131
|
+
}
|
|
132
|
+
const skip = new Set(skipSteps ?? []);
|
|
133
|
+
// Filter and resolve steps
|
|
134
|
+
const resolvedSteps = [];
|
|
135
|
+
let stepIndex = 1;
|
|
136
|
+
for (const step of pipeline.steps) {
|
|
137
|
+
// Determine skip key: tool name for mcp_call, description for agent_task
|
|
138
|
+
const skipKey = step.type === "mcp_call" ? step.tool : step.description;
|
|
139
|
+
if (skip.has(skipKey))
|
|
140
|
+
continue;
|
|
141
|
+
const base = {
|
|
142
|
+
step: stepIndex++,
|
|
143
|
+
type: step.type,
|
|
144
|
+
description: step.description,
|
|
145
|
+
on_error: step.on_error ?? "halt",
|
|
146
|
+
requires_approval: step.requires_approval ?? false,
|
|
147
|
+
};
|
|
148
|
+
if (step.type === "mcp_call") {
|
|
149
|
+
base.tool = step.tool;
|
|
150
|
+
base.params = substituteInParams(step.params, variables);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// agent_task — resolve instruction
|
|
154
|
+
let rawInstruction;
|
|
155
|
+
if (step.instruction_file) {
|
|
156
|
+
const content = instructions[step.instruction_file];
|
|
157
|
+
if (content === undefined) {
|
|
158
|
+
throw new Error(`Instruction file "${step.instruction_file}" not found in bundled instructions.`);
|
|
159
|
+
}
|
|
160
|
+
rawInstruction = content;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
rawInstruction = step.instruction;
|
|
164
|
+
}
|
|
165
|
+
base.instruction = substituteVariables(rawInstruction, variables);
|
|
166
|
+
}
|
|
167
|
+
resolvedSteps.push(base);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
pipeline: pipeline.name,
|
|
171
|
+
description: pipeline.description ?? "",
|
|
172
|
+
total_steps: resolvedSteps.length,
|
|
173
|
+
steps: resolvedSteps,
|
|
174
|
+
};
|
|
175
|
+
}
|