{
  "name": "Nodbot - MCP internal API tool gateway",
  "nodes": [
    {
      "parameters": {},
      "id": "5e0e1ff0-6034-498a-b0e1-42660e02cff4",
      "name": "MCP tool webhook",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        0,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const input = $json.body ?? $json;\nconst allowedTools = {\n  'crm.find_customer': {\n    roles: ['support_agent', 'admin'],\n    required: ['phone'],\n    max_rows: 3,\n    endpoint: '/crm/customers/search'\n  },\n  'orders.get_status': {\n    roles: ['support_agent', 'admin'],\n    required: ['order_id'],\n    max_rows: 1,\n    endpoint: '/orders/status'\n  }\n};\n\nconst spec = allowedTools[input.tool];\nif (!spec) throw new Error(`Tool is not allowed: ${input.tool}`);\n\nconst role = input.actor?.role;\nif (!spec.roles.includes(role)) {\n  throw new Error(`Role ${role} cannot call ${input.tool}`);\n}\n\nconst args = input.args ?? {};\nfor (const field of spec.required) {\n  if (!args[field]) throw new Error(`Missing required arg: ${field}`);\n}\n\nconst phone = args.phone ? String(args.phone).replace(/\\D/g, '') : undefined;\nif (phone && !/^(7|8)\\d{10}$/.test(phone)) {\n  throw new Error('Invalid phone format for tool call');\n}\n\nreturn [{\n  json: {\n    request_id: input.request_id,\n    tool: input.tool,\n    actor: input.actor,\n    endpoint: spec.endpoint,\n    method: 'POST',\n    sanitized_args: { ...args, phone: phone ? `+7${phone.slice(-10)}` : undefined },\n    limit: Math.min(input.policy?.max_rows ?? spec.max_rows, spec.max_rows),\n    audit: {\n      reason: input.policy?.reason ?? 'not_provided',\n      environment: input.policy?.environment ?? 'production',\n      checked_at: new Date().toISOString()\n    }\n  }\n}];"
      },
      "id": "5d867071-34c2-4f04-bde3-52116c049cfb",
      "name": "Validate allowlist and args",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        260
      ]
    },
    {
      "parameters": {},
      "id": "b630b208-0489-4501-b7ca-10f48c116b36",
      "name": "Write audit log",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        560,
        260
      ]
    },
    {
      "parameters": {},
      "id": "2abab63d-ff02-4cd1-bcd8-035d678b702e",
      "name": "Call internal API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        840,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const input = $json.body ?? $json;\nconst allowedTools = {\n  'crm.find_customer': {\n    roles: ['support_agent', 'admin'],\n    required: ['phone'],\n    max_rows: 3,\n    endpoint: '/crm/customers/search'\n  },\n  'orders.get_status': {\n    roles: ['support_agent', 'admin'],\n    required: ['order_id'],\n    max_rows: 1,\n    endpoint: '/orders/status'\n  }\n};\n\nconst spec = allowedTools[input.tool];\nif (!spec) throw new Error(`Tool is not allowed: ${input.tool}`);\n\nconst role = input.actor?.role;\nif (!spec.roles.includes(role)) {\n  throw new Error(`Role ${role} cannot call ${input.tool}`);\n}\n\nconst args = input.args ?? {};\nfor (const field of spec.required) {\n  if (!args[field]) throw new Error(`Missing required arg: ${field}`);\n}\n\nconst phone = args.phone ? String(args.phone).replace(/\\D/g, '') : undefined;\nif (phone && !/^(7|8)\\d{10}$/.test(phone)) {\n  throw new Error('Invalid phone format for tool call');\n}\n\nreturn [{\n  json: {\n    request_id: input.request_id,\n    tool: input.tool,\n    actor: input.actor,\n    endpoint: spec.endpoint,\n    method: 'POST',\n    sanitized_args: { ...args, phone: phone ? `+7${phone.slice(-10)}` : undefined },\n    limit: Math.min(input.policy?.max_rows ?? spec.max_rows, spec.max_rows),\n    audit: {\n      reason: input.policy?.reason ?? 'not_provided',\n      environment: input.policy?.environment ?? 'production',\n      checked_at: new Date().toISOString()\n    }\n  }\n}];"
      },
      "id": "789805cf-5007-4aa7-8338-4b9725ef9978",
      "name": "Sanitize response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        260
      ]
    },
    {
      "parameters": {},
      "id": "6672bcc2-cc56-482d-8208-04ed842ffee1",
      "name": "Respond to MCP client",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 2,
      "position": [
        1400,
        260
      ]
    }
  ],
  "connections": {
    "MCP tool webhook": {
      "main": [
        [
          {
            "node": "Validate allowlist and args",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate allowlist and args": {
      "main": [
        [
          {
            "node": "Write audit log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write audit log": {
      "main": [
        [
          {
            "node": "Call internal API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call internal API": {
      "main": [
        [
          {
            "node": "Sanitize response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sanitize response": {
      "main": [
        [
          {
            "node": "Respond to MCP client",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    "nodbot",
    "production",
    "template"
  ],
  "triggerCount": 1,
  "updatedAt": "2026-05-30T00:00:00.000Z",
  "versionId": "7c787189-9d4a-4739-8eae-704f1d8a5cbd",
  "meta": {
    "templateCredsSetupCompleted": false
  }
}