This commit is contained in:
bipproduction
2025-11-19 11:15:11 +08:00
parent dac1301f37
commit b2bb780a5a
4 changed files with 405 additions and 43 deletions

View File

@@ -50,15 +50,11 @@ type JSONRPCResponse = {
jsonrpc: "2.0";
id: string | number;
result?: any;
error?: {
code: number;
message: string;
data?: any;
};
error?: { code: number; message: string; data?: any };
};
// ======================================================
// Eksekusi Tool HTTP
// EXECUTE TOOL — SUPPORT PATH, QUERY, HEADER, BODY, COOKIE
// ======================================================
async function executeTool(
tool: any,
@@ -68,24 +64,75 @@ async function executeTool(
) {
const x = tool["x-props"] || {};
const method = (x.method || "GET").toUpperCase();
const path = x.path || `/${tool.name}`;
const url = `${baseUrl}${path}`;
let path = x.path || `/${tool.name}`;
const opts: RequestInit = {
method,
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
const query: Record<string, any> = {};
const headers: Record<string, any> = {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
};
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
opts.body = JSON.stringify(args || {});
let bodyPayload: any = undefined;
// ======================================================
// Pisahkan args berdasarkan OpenAPI parameter location
// ======================================================
if (Array.isArray(x.parameters)) {
for (const p of x.parameters) {
const name = p.name;
const value = args[name];
if (value === undefined) continue;
switch (p.in) {
case "path":
path = path.replace(`{${name}}`, encodeURIComponent(value));
break;
case "query":
query[name] = value;
break;
case "header":
headers[name] = value;
break;
case "cookie":
headers["Cookie"] = `${name}=${value}`;
break;
case "body":
case "requestBody":
bodyPayload = value;
break;
default:
break;
}
}
} else {
// fallback → semua args dianggap body
bodyPayload = args;
}
// ======================================================
// Build Final URL
// ======================================================
let url = `${baseUrl}${path}`;
const qs = new URLSearchParams(query).toString();
if (qs) url += `?${qs}`;
// ======================================================
// Build Request Options
// ======================================================
const opts: RequestInit = { method, headers };
if (["POST", "PUT", "PATCH", "DELETE"].includes(method) && bodyPayload !== undefined) {
opts.body = JSON.stringify(bodyPayload);
}
console.log(`[MCP] → Calling ${method} ${url}`);
const res = await fetch(url, opts);
const contentType = res.headers.get("content-type") || "";
const data = contentType.includes("application/json")
? await res.json()
: await res.text();
@@ -94,6 +141,7 @@ async function executeTool(
success: res.ok,
status: res.status,
method,
url,
path,
data,
};
@@ -168,17 +216,16 @@ async function handleMCPRequest(
token
);
const data = result.data.data;
const isObject = typeof data === "object" && data !== null;
const content = result.data?.data ?? result.data;
return {
jsonrpc: "2.0",
id,
result: {
content: [
isObject
? { type: "json", data }
: { type: "text", text: JSON.stringify(data || result.data || result) },
typeof content === "object"
? { type: "json", data: content }
: { type: "text", text: JSON.stringify(content) },
],
},
};
@@ -214,9 +261,7 @@ export class OpenapiMcpServer implements INodeType {
version: 1,
description: 'Runs an MCP Server inside n8n',
icon: 'file:icon.svg',
defaults: {
name: 'OpenAPI MCP Server'
},
defaults: { name: 'OpenAPI MCP Server' },
credentials: [
{ name: "openapiMcpServerCredentials", required: true },
],