import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
const MAKE_API_KEY = process.env.MAKE_DOT_COM_API_KEY!;
const MAKE_BASE_URL = process.env.MAKE_DOT_COM_BASE_URL || "eu2.make.com";
const MAKE_TEAM_ID = process.env.MAKE_DOT_COM_TEAM_ID;
if (!MAKE_API_KEY || !MAKE_TEAM_ID) {
throw new Error("Missing Make.com credentials or team ID");
}
function makeHeaders() {
return {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
};
}
// ========== Zod Schemas ==========
const ScenarioIdSchema = z.object({
scenario_id: z.number(),
draft: z.boolean().optional().default(false),
});
const CreateScenarioSchema = z.object({
name: z.string(),
folderId: z.number().optional(),
description: z.string().optional(),
});
const UpdateScenarioSchema = z.object({
scenario_id: z.number(),
name: z.string().optional(),
description: z.string().optional(),
module_id: z.number().optional(),
operations: z
.array(
z.object({
path: z.string(),
value: z.string(),
})
)
.optional(),
draft: z.boolean().optional().default(false),
});
const DescribeModuleSchema = z.object({
scenario_id: z.number(),
module_id: z.number(),
draft: z.boolean().optional().default(false),
});
const CheckModuleSchema = z.object({
scenario_id: z.number(),
module_id: z.number(),
draft: z.boolean().optional().default(false),
});
// ========== Tool List ==========
const tools = [
{
name: "read_make_dot_com_scenario_blueprint",
description: "Retrieve blueprint for a scenario",
inputSchema: zodToJsonSchema(ScenarioIdSchema),
},
{
name: "create_make_dot_com_scenario",
description: "Create a new Make.com scenario",
inputSchema: zodToJsonSchema(CreateScenarioSchema),
},
{
name: "update_make_dot_com_scenario",
description: "Update scenario name/description and optionally patch a module",
inputSchema: zodToJsonSchema(UpdateScenarioSchema),
},
{
name: "describe_make_dot_com_module",
description: "Describe module parameters from blueprint",
inputSchema: zodToJsonSchema(DescribeModuleSchema),
},
{
name: "check_make_dot_com_module_data",
description: "Check if module required fields are filled",
inputSchema: zodToJsonSchema(CheckModuleSchema),
},
{
name: "list_make_dot_com_scenarios",
description: "List available Make.com scenarios",
inputSchema: { type: "object", properties: {}, required: [] },
},
{
name: "list_make_dot_com_connections",
description: "List Make.com connections and their validity",
inputSchema: { type: "object", properties: {}, required: [] },
},
];
// ========== Core Functions ==========
function applyPatch(target: any, path: string, value: any) {
const keys = path.split(".");
let current = target;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current)) current[key] = {};
current = current[key];
}
current[keys[keys.length - 1]] = value;
}
async function getBlueprint(scenario_id: number, draft: boolean = false) {
const url = `https://${MAKE_BASE_URL}/api/v2/scenarios/${scenario_id}/blueprint?includeDraft=${draft}&teamId=${MAKE_TEAM_ID}`;
console.error("📥 Fetching Blueprint...");
console.error("🔗 URL:", url);
console.error("🧾 Headers:", makeHeaders());
const res = await fetch(url, { headers: makeHeaders() });
if (!res.ok) {
const text = await res.text();
throw new Error(`Failed to get blueprint: ${res.status} ${res.statusText} - ${text}`);
}
const data = await res.json();
return data.response?.blueprint || data;
}
async function getModule(scenario_id: number, module_id: number, draft: boolean = false) {
const blueprint = await getBlueprint(scenario_id, draft);
const module = blueprint.flow?.find((m: any) => m.id === module_id);
if (!module) throw new Error(`Module ${module_id} not found in blueprint`);
return module;
}
async function createScenario(name: string, folderId?: number, description?: string) {
const res = await fetch(`https://${MAKE_BASE_URL}/api/v2/scenarios`, {
method: "POST",
headers: makeHeaders(),
body: JSON.stringify({ name, folderId, description, teamId: MAKE_TEAM_ID }),
});
if (!res.ok) throw new Error(`Failed to create scenario`);
return await res.json();
}
async function updateScenario(
scenario_id: number,
{ name, description, module_id, operations, draft = false }: any
) {
let blueprintUpdate = {};
if (module_id && operations?.length) {
const blueprint = await getBlueprint(scenario_id, draft);
const module = blueprint.flow.find((m: any) => m.id === module_id);
if (!module) throw new Error(`Module ${module_id} not found`);
const patchModule = {
id: module.id,
module: module.module,
version: module.version,
parameters: module.parameters,
mapper: { ...module.mapper },
};
for (const { path, value } of operations) {
applyPatch(patchModule, path, value);
}
const updatedBlueprint = {
...blueprint,
flow: [patchModule],
};
blueprintUpdate = {
blueprint: JSON.stringify(updatedBlueprint),
};
console.error("🛠 PatchModule Blueprint:");
console.error(JSON.stringify(updatedBlueprint, null, 2));
}
const payload = {
name,
description,
...blueprintUpdate,
};
const url = `https://${MAKE_BASE_URL}/api/v2/scenarios/${scenario_id}?teamId=${MAKE_TEAM_ID}`;
console.error("🚀 PATCH Scenario");
console.error("🔗 URL:", url);
console.error("🧾 Headers:", makeHeaders());
console.error("📦 Payload:", JSON.stringify(payload, null, 2));
const res = await fetch(url, {
method: "PATCH",
headers: makeHeaders(),
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text();
console.error("❌ PATCH Failed:", res.status, res.statusText, text);
throw new Error(`Failed to update scenario: ${text}`);
}
const json = await res.json();
console.error("✅ PATCH Success:", JSON.stringify(json, null, 2));
return json;
}
async function listScenarios() {
const res = await fetch(
`https://${MAKE_BASE_URL}/api/v2/scenarios?teamId=${MAKE_TEAM_ID}`,
{ headers: makeHeaders() }
);
if (!res.ok) throw new Error("Failed to list scenarios");
return await res.json();
}
async function listConnections() {
const url = `https://${MAKE_BASE_URL}/api/v2/connections?teamId=${MAKE_TEAM_ID}`;
const res = await fetch(url, { headers: makeHeaders() });
if (!res.ok) {
throw new Error(`Failed to list connections: ${res.status} ${res.statusText}`);
}
return await res.json();
}
// ========== Server ==========
const server = new Server(
{ name: "mcp-server-make-dot-com", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
server.setRequestHandler(CallToolRequestSchema, async ({ params: { name, arguments: args } }) => {
switch (name) {
case "read_make_dot_com_scenario_blueprint": {
const { scenario_id, draft } = ScenarioIdSchema.parse(args);
const blueprint = await getBlueprint(scenario_id, draft);
return { content: [{ type: "text", text: JSON.stringify(blueprint, null, 2) }] };
}
case "create_make_dot_com_scenario": {
const { name, folderId, description } = CreateScenarioSchema.parse(args);
const result = await createScenario(name, folderId, description);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
case "update_make_dot_com_scenario": {
const data = UpdateScenarioSchema.parse(args);
const result = await updateScenario(data.scenario_id, data);
return { content: [{ type: "text", text: `✅ Scenario updated\n${JSON.stringify(result, null, 2)}` }] };
}
case "describe_make_dot_com_module": {
const { scenario_id, module_id, draft } = DescribeModuleSchema.parse(args);
const module = await getModule(scenario_id, module_id, draft);
return {
content: [{ type: "text", text: JSON.stringify(module, null, 2) }],
};
}
case "check_make_dot_com_module_data": {
const { scenario_id, module_id, draft } = CheckModuleSchema.parse(args);
const module = await getModule(scenario_id, module_id, draft);
const required = module.metadata?.interface?.filter((f: any) => f.required)?.map((f: any) => f.name) || [];
const filled = required.filter((name: string) => module.parameters?.[name] !== undefined);
return {
content: [{ type: "text", text: `${filled.length}/${required.length} required fields filled.` }],
};
}
case "list_make_dot_com_scenarios": {
const data = await listScenarios();
const result = data?.scenarios?.map((s: any) => `• ${s.name} (ID: ${s.id})`)?.join("\n") || "No scenarios found.";
return { content: [{ type: "text", text: result }] };
}
case "list_make_dot_com_connections": {
const data = await listConnections();
const result = data.map((c: any) => `• ${c.label || c.name} (${c.id}) ${c.valid ? "✅" : "❌"}`).join("\n");
return { content: [{ type: "text", text: result }] };
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// ========== Start Server ==========
(async () => {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("✅ Make.com MCP Server running...");
})();