#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
const UpdateModuleParamsSchema = z.object({
scenario_id: z.number(),
module_id: z.number(),
parameters: z.record(z.unknown()),
draft: z.boolean().optional().default(false),
});
const READ_SCENARIO_BLUEPRINT_TOOL: Tool = {
name: "read_make_dot_com_scenario_blueprint",
description:
"Reads the JSON blueprint of a Make.com scenario. Returns the complete blueprint structure including flow, connections, and metadata.",
inputSchema: {
type: "object",
properties: {
scenario_id: {
type: "number",
description: "Scenario ID to retrieve the blueprint for",
},
draft: {
type: "boolean",
description:
"If true, retrieves the draft version. If false, retrieves the live version.",
default: false,
},
},
required: ["scenario_id"],
},
};
const LIST_SCENARIOS_TOOL: Tool = {
name: "list_make_dot_com_scenarios",
description: "Lists all available Make.com scenarios with their IDs, names, and scheduling types.",
inputSchema: {
type: "object",
properties: {},
},
};
const DESCRIBE_MAKE_MODULE_TOOL: Tool = {
name: "describe_make_dot_com_module",
description: "Describes the configuration and parameters of a module inside a Make.com scenario.",
inputSchema: {
type: "object",
properties: {
scenario_id: {
type: "number",
description: "ID of the scenario containing the module",
},
module_id: {
type: "number",
description: "ID of the module to describe",
},
draft: {
type: "boolean",
description: "If true, retrieve from the draft version",
default: false,
},
},
required: ["scenario_id", "module_id"],
},
};
const CREATE_SCENARIO_TOOL: Tool = {
name: "create_make_dot_com_scenario",
description: "Creates a new Make.com scenario with optional folder and description",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "Name of the new scenario" },
folderId: { type: "number", description: "Folder ID to create scenario in" },
description: { type: "string", description: "Optional scenario description" },
},
required: ["name"],
},
};
const UPDATE_SCENARIO_TOOL: Tool = {
name: "update_make_dot_com_scenario",
description: "Updates a Make.com scenario's name, description, or scheduling.",
inputSchema: {
type: "object",
properties: {
scenario_id: { type: "number", description: "ID of the scenario to update" },
name: { type: "string", description: "New name of the scenario" },
description: { type: "string", description: "New description of the scenario" },
},
required: ["scenario_id"],
},
};
const CHECK_MODULE_DATA_TOOL: Tool = {
name: "check_make_dot_com_module_data",
description: "Checks whether a module in a Make.com scenario has required configuration fields populated.",
inputSchema: {
type: "object",
properties: {
scenario_id: { type: "number", description: "Scenario ID containing the module" },
module_id: { type: "number", description: "Module ID to verify" },
draft: { type: "boolean", default: false },
},
required: ["scenario_id", "module_id"],
},
};
const UPDATE_MODULE_PARAMETERS_TOOL: Tool = {
name: "update_module_parameters",
description: "Updates specific parameters of a module inside a Make.com scenario blueprint.",
inputSchema: {
type: "object",
properties: {
scenario_id: { type: "number", description: "ID of the scenario" },
module_id: { type: "number", description: "ID of the module to update" },
parameters: {
type: "object",
additionalProperties: true,
description: "Parameters to update as key-value pairs",
},
draft: { type: "boolean", default: false },
},
required: ["scenario_id", "module_id", "parameters"],
},
};
const LIST_CONNECTIONS_TOOL: Tool = {
name: "list_make_dot_com_connections",
description: "Lists all available connections for the team and whether they are valid.",
inputSchema: {
type: "object",
properties: {},
},
};
const server = new Server(
{
name: "mcp-server-make-dot-com",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
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) {
console.error("Error: MAKE_DOT_COM_API_KEY environment variable is required");
process.exit(1);
}
if (!MAKE_TEAM_ID) {
console.error("Error: MAKE_DOT_COM_TEAM_ID environment variable is required");
process.exit(1);
}
async function updateScenario(
scenario_id: number,
name?: string,
description?: string
): Promise<string> {
const url = `https://${MAKE_BASE_URL}/api/v2/scenarios/${scenario_id}?teamId=${MAKE_TEAM_ID}`;
const body = {
...(name && { name }),
...(description && { description }),
};
const response = await fetch(url, {
method: "PATCH",
headers: {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(
`Make.com API error: ${response.status} ${response.statusText}\n${await response.text()}`
);
}
const updated = await response.json();
return `✅ Scenario updated: ${updated.name} (ID: ${updated.id})`;
}
async function checkModuleData(scenario_id: number, module_id: number, draft = false): Promise<string> {
const url = `https://${MAKE_BASE_URL}/api/v2/scenarios/${scenario_id}/blueprint?includeDraft=${draft}&teamId=${MAKE_TEAM_ID}`;
const response = await fetch(url, {
headers: {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(`Make.com API error: ${response.status} ${response.statusText}\n${await response.text()}`);
}
const data = await response.json();
const module = data.response.blueprint.flow.find((m: any) => m.id === module_id);
if (!module) throw new Error(`Module ${module_id} not found.`);
const requiredFields = module.metadata?.interface?.filter((f: any) => f.required).map((f: any) => f.name) || [];
const filled = requiredFields.filter((field: string) => module.parameters?.[field] !== undefined);
return `Module ${module_id} config: ${filled.length}/${requiredFields.length} required fields filled.`;
}
async function updateModuleParameters(
scenario_id: number,
module_id: number,
parameters: Record<string, unknown>,
draft = false
): Promise<string> {
const blueprintUrl = `https://${MAKE_BASE_URL}/api/v2/scenarios/${scenario_id}/blueprint?includeDraft=${draft}&teamId=${MAKE_TEAM_ID}`;
const blueprintResponse = await fetch(blueprintUrl, {
headers: {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
});
if (!blueprintResponse.ok) {
throw new Error(`Make.com API error: ${blueprintResponse.status} ${blueprintResponse.statusText}\n${await blueprintResponse.text()}`);
}
const data = await blueprintResponse.json();
const blueprint = data.response.blueprint;
const module = blueprint.flow.find((m: any) => m.id === module_id);
if (!module) throw new Error(`Module ${module_id} not found.`);
module.parameters = { ...module.parameters, ...parameters };
const updateResponse = await fetch(blueprintUrl, {
method: "PATCH",
headers: {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ blueprint }),
});
if (!updateResponse.ok) {
throw new Error(`Failed to update module parameters: ${updateResponse.status} ${updateResponse.statusText}\n${await updateResponse.text()}`);
}
return `✅ Module ${module_id} parameters updated successfully.`;
}
async function listConnections(): Promise<string> {
const url = `https://${MAKE_BASE_URL}/api/v2/connections?teamId=${MAKE_TEAM_ID}`;
const response = await fetch(url, {
headers: {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(
`Make.com API error: ${response.status} ${response.statusText}\n${await response.text()}`
);
}
const data = await response.json();
if (!Array.isArray(data)) return "No connections found.";
const connections = data.map((conn: any) => {
const name = conn.label || conn.name || conn.id;
const valid = conn.valid === true ? "✅ valid" : "❌ invalid";
return `• ${name} (${conn.id}) - ${valid}`;
});
return connections.join("\n") || "No connections found.";
}
interface MakeBlueprint {
code: string;
response: {
blueprint: {
flow: Array<{
id: number;
module: string;
version: number;
parameters: Record<string, unknown>;
mapper?: Record<string, unknown>;
metadata?: Record<string, any>;
[key: string]: unknown;
}>;
[key: string]: unknown;
};
};
}
interface MakeScenario {
id: number;
name: string;
scheduling: {
type: string;
};
}
function isMakeScenarioBlueprintArgs(
args: unknown
): args is { scenario_id: number; draft?: boolean } {
return (
typeof args === "object" &&
args !== null &&
"scenario_id" in args &&
typeof (args as { scenario_id: number }).scenario_id === "number"
);
}
function isDescribeModuleArgs(
args: unknown
): args is { scenario_id: number; module_id: number; draft?: boolean } {
return (
typeof args === "object" &&
args !== null &&
"scenario_id" in args &&
typeof (args as any).scenario_id === "number" &&
"module_id" in args &&
typeof (args as any).module_id === "number"
);
}
async function getScenarioBlueprint(
scenarioId: number,
draft: boolean = false
): Promise<MakeBlueprint["response"]["blueprint"]> {
const url = `https://${MAKE_BASE_URL}/api/v2/scenarios/${scenarioId}/blueprint?includeDraft=${draft}&teamId=${MAKE_TEAM_ID}`;
const response = await fetch(url, {
headers: {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(
`Make.com API error: ${response.status} ${response.statusText}\n${await response.text()}`
);
}
const data = (await response.json()) as MakeBlueprint;
return data.response.blueprint;
}
async function listScenarios(): Promise<string> {
const url = `https://${MAKE_BASE_URL}/api/v2/scenarios?teamId=${MAKE_TEAM_ID}`;
const response = await fetch(url, {
headers: {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(
`Make.com API error: ${response.status} ${response.statusText}\n${await response.text()}`
);
}
const data = await response.json();
const scenarios = data.scenarios as MakeScenario[];
const summary = scenarios.map((s) => `• ${s.name} (ID: ${s.id}, Type: ${s.scheduling.type})`).join("\n");
return summary || "No scenarios found.";
}
function describeModule(blueprint: MakeBlueprint["response"]["blueprint"], module_id: number): string {
const mod = blueprint.flow.find((m) => m.id === module_id);
if (!mod) {
throw new Error(`Module with ID ${module_id} not found in scenario.`);
}
const lines: string[] = [
`📦 Module: ${mod.module} (id: ${mod.id})`,
mod.metadata?.interface
? `🔧 Inputs: ${mod.metadata.interface.map((i: any) => `${i.name} (${i.type})${i.required ? ' *' : ''}`).join(", ")}`
: `🔧 Parameters: ${Object.keys(mod.parameters).join(", ")}`,
mod.mapper ? `🧠 Mapper Fields: ${Object.keys(mod.mapper).join(", ")}` : "(No mapping info)"
];
return lines.join("\n");
}
async function createScenario(name: string, folderId?: number, description?: string): Promise<string> {
const url = `https://${MAKE_BASE_URL}/api/v2/scenarios`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Token ${MAKE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
name,
description,
folderId,
teamId: MAKE_TEAM_ID,
}),
});
if (!response.ok) {
throw new Error(`Failed to create scenario: ${response.status} ${response.statusText}\n${await response.text()}`);
}
const data = await response.json();
return `✅ Scenario created: ${data.name} (ID: ${data.id})`;
}
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
READ_SCENARIO_BLUEPRINT_TOOL,
LIST_SCENARIOS_TOOL,
DESCRIBE_MAKE_MODULE_TOOL,
CREATE_SCENARIO_TOOL,
UPDATE_SCENARIO_TOOL,
CHECK_MODULE_DATA_TOOL,
UPDATE_MODULE_PARAMETERS_TOOL,
LIST_CONNECTIONS_TOOL,
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args = {} } = request.params;
if (name === READ_SCENARIO_BLUEPRINT_TOOL.name) {
if (!isMakeScenarioBlueprintArgs(args)) {
console.error("Invalid arguments for read_make_dot_com_scenario_blueprint");
throw new Error("Invalid arguments");
}
try {
const blueprint = await getScenarioBlueprint(args.scenario_id, args.draft);
return {
content: [{ type: "text", text: JSON.stringify(blueprint, null, 2) }],
};
} catch (error) {
console.error(`Error processing scenario ${args.scenario_id}:`, error);
throw error;
}
}
if (name === LIST_SCENARIOS_TOOL.name) {
try {
const result = await listScenarios();
return {
content: [{ type: "text", text: result }],
};
} catch (error) {
console.error("Error listing scenarios:", error);
throw error;
}
}
if (name === DESCRIBE_MAKE_MODULE_TOOL.name) {
if (!isDescribeModuleArgs(args)) {
console.error("Invalid arguments for describe_make_dot_com_module");
throw new Error("Invalid arguments");
}
try {
const blueprint = await getScenarioBlueprint(args.scenario_id, args.draft);
const description = describeModule(blueprint, args.module_id);
return {
content: [{ type: "text", text: description }],
};
} catch (error) {
console.error("Error describing module:", error);
throw error;
}
}
if (name === CREATE_SCENARIO_TOOL.name) {
if (typeof args.name !== 'string') {
throw new Error("Scenario name must be a string");
}
try {
const folderId = typeof args.folderId === 'number' ? args.folderId : undefined;
const message = await createScenario(args.name, folderId, args.description as string | undefined);
return {
content: [{ type: "text", text: message }],
};
} catch (error) {
console.error("Error creating scenario:", error);
throw error;
}
}
if (name === UPDATE_SCENARIO_TOOL.name) {
if (typeof args.scenario_id !== 'number' || typeof args.name !== 'string' || typeof args.description !== 'string') {
throw new Error("Invalid arguments for update_make_dot_com_scenario");
}
try {
const message = await updateScenario(args.scenario_id, args.name, args.description);
return {
content: [{ type: "text", text: message }],
};
} catch (error) {
console.error("Error updating scenario:", error);
throw error;
}
}
if (name === CHECK_MODULE_DATA_TOOL.name) {
const { scenario_id, module_id, draft } = args as any;
try {
const result = await checkModuleData(scenario_id, module_id, draft);
return {
content: [{ type: "text", text: result }],
};
} catch (error) {
console.error("Error checking module data:", error);
throw error;
}
}
if (name === UPDATE_MODULE_PARAMETERS_TOOL.name) {
try {
const { scenario_id, module_id, parameters, draft } = UpdateModuleParamsSchema.parse(args);
const result = await updateModuleParameters(scenario_id, module_id, parameters, draft);
return {
content: [{ type: "text", text: result }],
};
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors.map(e => `${e.path.join(".")}: ${e.message}`).join(", ")}`
);
}
console.error("Error updating module parameters:", error);
throw error;
}
}
if (name === LIST_CONNECTIONS_TOOL.name) {
try {
const result = await listConnections();
return {
content: [{ type: "text", text: result }],
};
} catch (error) {
console.error("Error listing connections:", error);
throw error;
}
}
console.error(`Unknown tool requested: ${name}`);
throw new Error(`Unknown tool: ${name}`);
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Make.com MCP Server running on stdio");
}
runServer().catch((error) => {
console.error("Fatal error running server:", error);
process.exit(1);
});