模型上下文协议适配器

Blinksbuy MCP Adapter是一个通道无关的协议适配器,用于将原始客户端消息转换为规范JSON格式,以便与Model Context Protocol (MCP)平台进行交互,支持多租户、多语言和多种输入类型。

  • by wbernardo-star
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 wbernardo-star/blinksbuy_mcp_adapter

使用说明

Blinksbuy MCP适配器(规范JSON v1.1)

AI自动化平台——模型上下文协议适配器

版本:1.1·铁路部署就绪


概述

这 MCP适配器 是所有外部渠道(网络、移动、语音、聊天、WhatsApp、ElevenLabs等)进入 模型上下文协议(MCP)平台.

它转换 原始客户端消息 进入a 标准JSON信封(v1.1) MCP编排者理解。
它还将MCP的规范响应转换回上游系统可以使用的与通道无关的JSON格式(TTS、聊天客户端、UI等)

此适配器可确保 所有通道使用一个统一的协议,实现一致性:

  • 可观察性(跟踪/span/消息ID)
  • LLM提示上下文
  • 会话连续性
  • 微服务安全路由
  • 多租户行为
  • 未来的多模式输入(音频、图像、事件等)

主要特点

  • LLM就绪 –带有可选的LLM提示(型号、温度等)
  • 微服务安全 –下游故障的标准化误差包络。
  • 适配器无关 –适用于网络、语音、移动、WhatsApp等。
  • 可追溯的 – trace_id, span_id, message_id 对于每一条消息。
  • 版本可控 –规范信封v1.1,向后兼容v1.0。
  • 面向未来的多式联运 –支持 type: text | audio | image | event.

环境变量

变量 描述 示例
MCP_URL MCP编排器的URL /orchestrate https://mcp-service.up.railway.app/orchestrate
API_KEY 或 API_KEYS 一个或多个允许的API密钥 abc123,xyz789

认证

每一个规范请求 必须 包括:

X-API-Key: <your_key>

没有有效密钥的请求将返回:

  • 401 Unauthorized (无钥匙)
  • 403 Forbidden (无效密钥)

API终点

GET /health

基本健康检查。

答复:

{
  "status": "ok",
  "service": "mcp_adapter_python",
  "mcp_url": "https://mcp-service.up.railway.app/orchestrate"
}

POST /canonical/message (主要终点)

通道无关的规范消息网关。

标题:

X-API-Key: <your_key>
Content-Type: application/json

正文(规范请求信封v1.1–文本示例):

{
  "version": "1.1",
  "timestamp": "2025-11-21T21:28:56.146Z",
  "context": {
    "channel": "web",
    "device": "browser",
    "locale": "en-US",
    "tenant": "blinksbuy",
    "client_app": "elevenlabs",
    "llm": {
      "model_hint": "gpt-4.1-mini",
      "temperature": 0.2
    }
  },
  "session": {
    "session_id": "user-123:web",
    "conversation_id": "conv-001",
    "user_id": "user-123",
    "turn": 3
  },
  "request": {
    "type": "text",
    "text": "Can you read me the menu?",
    "intent_override": null,
    "metadata": {
      "raw_transcript": "can you read me the menu",
      "confidence": 0.97
    }
  },
  "observability": {
    "trace_id": "trace-abc-123",
    "span_id": "span-inbound-1",
    "message_id": "msg-0001"
  }
}

多模式示例(音频)

{
  "version": "1.1",
  "timestamp": "2025-11-21T21:30:00Z",
  "context": {
    "channel": "voice",
    "device": "iphone",
    "locale": "en-US",
    "tenant": "blinksbuy",
    "client_app": "elevenlabs"
  },
  "session": {
    "session_id": "voice-user-44",
    "conversation_id": "conv-331",
    "user_id": "voice-user-44",
    "turn": 12
  },
  "request": {
    "type": "audio",
    "audio_url": "https://cdn.you/audio.wav",
    "transcript": "I would like to order garlic chicken"
  },
  "observability": {
    "trace_id": "trace-22aa1bb3",
    "span_id": "span-991",
    "message_id": "msg-0002"
  }
}

规范响应(成功)

{
  "version": "1.1",
  "timestamp": "2025-11-21T21:28:56.146Z",
  "context": {
    "channel": "web",
    "device": "browser",
    "locale": "en-US",
    "tenant": "blinksbuy",
    "client_app": "elevenlabs"
  },
  "session": {
    "session_id": "user-123:web",
    "conversation_id": "conv-001",
    "user_id": "user-123",
    "turn": 4,
    "route": "food_ordering.menu"
  },
  "response": {
    "status": "success",
    "code": 200,
    "type": "text",
    "text": "Here is the menu from Uno Bistro:\n\n1. Garlic Chicken – 500\n2. Sizzling Pata – 650\n3. Sisig – 400",
    "metadata": {
      "source": "mcp_orchestrator",
      "duration_ms": 16250.315
    }
  },
  "error": null,
  "observability": {
    "trace_id": "trace-abc-123",
    "span_id": "span-outbound-1",
    "message_id": "msg-0003"
  }
}

规范响应(错误)

{
  "version": "1.1",
  "timestamp": "2025-11-21T21:28:56.146Z",
  "context": {
    "channel": "web",
    "device": "browser",
    "locale": "en-US",
    "tenant": "blinksbuy",
    "client_app": "elevenlabs"
  },
  "session": {
    "session_id": "user-123:web",
    "conversation_id": "conv-001",
    "user_id": "user-123",
    "turn": 4,
    "route": null
  },
  "response": {
    "status": "error",
    "code": 502,
    "type": "text",
    "text": null,
    "metadata": {
      "source": "mcp_adapter",
      "duration_ms": 112.529
    }
  },
  "error": {
    "type": "MCP_ORCHESTRATOR_ERROR",
    "code": 502,
    "message": "MCP orchestrator error: 503 Service Unavailable",
    "retryable": false,
    "details": {
      "mcp_url": "https://your-mcp-service.railway.app/orchestrate"
    }
  },
  "observability": {
    "trace_id": "trace-abc-123",
    "span_id": "span-error-1",
    "message_id": "msg-err-001"
  }
}

使用Postman进行测试

  1. 设置标题:
    • X-API-Key: <your_key>
    • Content-Type: application/json
  2. 邮寄至:
    • https://<your-railway-url>/canonical/message
  3. 粘贴规范请求JSON正文并发送。

客户端集成和代码小部件

本节将展示不同客户端如何向Blinksbuy MCP适配器发送规范请求。

所有示例都针对规范文本端点:

POST https://<your-railway-url>/canonical/message
X-API-Key: <your_key>
Content-Type: application/json

请求体使用上面显示的规范JSON信封v1.1结构。

Python(请求)

import requests
import uuid
from datetime import datetime, timezone

API_KEY = "adapter-super-secret-key-1"
BASE_URL = "https://<your-railway-url>"
ENDPOINT = f"{BASE_URL}/canonical/message"

def build_payload(user_id: str, text: str) -> dict:
    now = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
    session_id = f"{user_id}:web"
    conversation_id = f"conv-{uuid.uuid4()}"

    return {
        "version": "1.1",
        "timestamp": now,
        "context": {
            "channel": "web",
            "device": "browser",
            "locale": "en-US",
            "tenant": "blinksbuy",
            "client_app": "python-example",
            "llm": {
                "model_hint": "gpt-4.1-mini",
                "temperature": 0.2,
            },
        },
        "session": {
            "session_id": session_id,
            "conversation_id": conversation_id,
            "user_id": user_id,
            "turn": 1,
        },
        "request": {
            "type": "text",
            "text": text,
            "intent_override": None,
            "metadata": {
                "raw_transcript": text,
                "confidence": 1.0,
            },
        },
        "observability": {
            "trace_id": f"trace-{uuid.uuid4()}",
            "span_id": f"span-{uuid.uuid4()}",
            "message_id": f"msg-{uuid.uuid4()}",
        },
    }

def send_message(user_id: str, text: str):
    headers = {
        "X-API-Key": API_KEY,
        "Content-Type": "application/json",
    }
    payload = build_payload(user_id, text)
    resp = requests.post(ENDPOINT, json=payload, headers=headers, timeout=30)
    resp.raise_for_status()
    return resp.json()

if __name__ == "__main__":
    response = send_message("user-123", "Can you read me the menu?")
    print("Adapter response:")
    print(response)

Node.js(fetch)

import fetch from "node-fetch";
import { randomUUID } from "crypto";

const API_KEY = "adapter-super-secret-key-1";
const BASE_URL = "https://<your-railway-url>";
const ENDPOINT = `${BASE_URL}/canonical/message`;

function buildPayload(userId, text) {
  const now = new Date().toISOString();
  const sessionId = `${userId}:web`;
  const conversationId = `conv-${randomUUID()}`;

  return {
    version: "1.1",
    timestamp: now,
    context: {
      channel: "web",
      device: "node",
      locale: "en-US",
      tenant: "blinksbuy",
      client_app: "node-example",
      llm: {
        model_hint: "gpt-4.1-mini",
        temperature: 0.2,
      },
    },
    session: {
      session_id: sessionId,
      conversation_id: conversationId,
      user_id: userId,
      turn: 1,
    },
    request: {
      type: "text",
      text,
      intent_override: null,
      metadata: {
        raw_transcript: text,
        confidence: 1.0,
      },
    },
    observability: {
      trace_id: `trace-${randomUUID()}`,
      span_id: `span-${randomUUID()}`,
      message_id: `msg-${randomUUID()}`,
    },
  };
}

async function sendMessage(userId, text) {
  const payload = buildPayload(userId, text);

  const resp = await fetch(ENDPOINT, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  if (!resp.ok) {
    const errorText = await resp.text();
    throw new Error(`Adapter error ${resp.status}: ${errorText}`);
  }

  const data = await resp.json();
  console.log("Adapter response:", data);
}

sendMessage("user-123", "Can you read me the menu?").catch(console.error);

浏览器HTML+JavaScript

注意:对于生产,不要在客户端代码中直接公开您的API密钥;使用后端代理。此示例用于内部测试和演示。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Blinksbuy MCP Adapter Demo</title>
</head>
<body>
  <h1>Blinksbuy MCP Adapter Demo</h1>

  <label>
    Message:
    <input id="user-input" type="text" value="Can you read me the menu?" />
  </label>
  <button id="send-btn">Send</button>

  <pre id="response-box"></pre>

  <script>
    const API_KEY = "adapter-super-secret-key-1"; // Do not expose in production
    const BASE_URL = "https://<your-railway-url>";
    const ENDPOINT = BASE_URL + "/canonical/message";

    function buildPayload(userId, text) {
      const now = new Date().toISOString();
      const sessionId = userId + ":web";
      const conversationId = "conv-" + crypto.randomUUID();

      return {
        version: "1.1",
        timestamp: now,
        context: {
          channel: "web",
          device: "browser",
          locale: "en-US",
          tenant: "blinksbuy",
          client_app: "browser-example",
          llm: {
            model_hint: "gpt-4.1-mini",
            temperature: 0.2,
          },
        },
        session: {
          session_id: sessionId,
          conversation_id: conversationId,
          user_id: userId,
          turn: 1,
        },
        request: {
          type: "text",
          text: text,
          intent_override: null,
          metadata: {
            raw_transcript: text,
            confidence: 1.0,
          },
        },
        observability: {
          trace_id: "trace-" + crypto.randomUUID(),
          span_id: "span-" + crypto.randomUUID(),
          message_id: "msg-" + crypto.randomUUID(),
        },
      };
    }

    async function sendMessage(text) {
      const userId = "web-demo-user";
      const payload = buildPayload(userId, text);

      const resp = await fetch(ENDPOINT, {
        method: "POST",
        headers: {
          "X-API-Key": API_KEY,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
      });

      const responseBox = document.getElementById("response-box");

      if (!resp.ok) {
        const errorText = await resp.text();
        responseBox.textContent = "Error " + resp.status + ":\n" + errorText;
        return;
      }

      const data = await resp.json();
      responseBox.textContent = JSON.stringify(data, null, 2);
    }

    document.getElementById("send-btn").addEventListener("click", () => {
      const text = document.getElementById("user-input").value;
      sendMessage(text).catch((err) => {
        document.getElementById("response-box").textContent = String(err);
      });
    });
  </script>
</body>
</html>

PowerShell(调用RestMethod)

$endpoint = "https://<your-railway-url>/canonical/message"
$apiKey   = "adapter-super-secret-key-1"

$userId         = "ps-user-" + [guid]::NewGuid().ToString()
$sessionId      = "$userId:web"
$conversationId = "conv-" + [guid]::NewGuid().ToString()
$traceId        = "trace-" + [guid]::NewGuid().ToString()
$spanId         = "span-" + [guid]::NewGuid().ToString()
$messageId      = "msg-" + [guid]::NewGuid().ToString()

$text = "Hello from PowerShell. Can you read me the menu?"

$headers = @{
    "Content-Type" = "application/json"
    "X-API-Key"    = $apiKey
}

$payload = @{
    version   = "1.1"
    timestamp = (Get-Date).ToUniversalTime().ToString("o")
    context   = @{
        channel    = "web"
        device     = "powershell"
        locale     = "en-US"
        tenant     = "blinksbuy"
        client_app = "powershell-client"
        llm = @{
            model_hint  = "gpt-4.1-mini"
            temperature = 0.2
        }
    }
    session = @{
        session_id      = $sessionId
        conversation_id = $conversationId
        user_id         = $userId
        turn            = 1
    }
    request = @{
        type = "text"
        text = $text
        metadata = @{
            raw_transcript = $text
            confidence     = 1.0
        }
    }
    observability = @{
        trace_id   = $traceId
        span_id    = $spanId
        message_id = $messageId
    }
}

$json = $payload | ConvertTo-Json -Depth 10
$response = Invoke-RestMethod -Uri $endpoint -Method Post -Body $json -Headers $headers
$response | ConvertTo-Json -Depth 10

邮差收藏(JSON)

您可以将以下收藏导入Postman(导入→ 原始文本):

{
  "info": {
    "name": "Blinksbuy MCP Adapter",
    "_postman_id": "d5fd2fb2-9e09-49e3-9c8a-338b4a43c5bb",
    "description": "Postman collection for testing the Blinksbuy MCP-compatible canonical message endpoint.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Canonical Message (Text)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "X-API-Key",
            "value": "{{API_KEY}}",
            "type": "text"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"version\": \"1.1\",\n  \"timestamp\": \"{{timestamp}}\",\n  \"context\": {\n    \"channel\": \"web\",\n    \"device\": \"postman\",\n    \"locale\": \"en-US\",\n    \"tenant\": \"blinksbuy\",\n    \"client_app\": \"postman-client\",\n    \"llm\": {\n      \"model_hint\": \"gpt-4.1-mini\",\n      \"temperature\": 0.2\n    }\n  },\n  \"session\": {\n    \"session_id\": \"{{session_id}}\",\n    \"conversation_id\": \"{{conversation_id}}\",\n    \"user_id\": \"{{user_id}}\",\n    \"turn\": 1\n  },\n  \"request\": {\n    \"type\": \"text\",\n    \"text\": \"Hello from Postman. Can you read me the menu?\",\n    \"intent_override\": null,\n    \"metadata\": {\n      \"raw_transcript\": \"Hello from Postman. Can you read me the menu?\",\n      \"confidence\": 1.0\n    }\n  },\n  \"observability\": {\n    \"trace_id\": \"{{trace_id}}\",\n    \"span_id\": \"{{span_id}}\",\n    \"message_id\": \"{{message_id}}\"\n  }\n}"
        },
        "url": {
          "raw": "{{BASE_URL}}/canonical/message",
          "host": [
            "{{BASE_URL}}"
          ],
          "path": [
            "canonical",
            "message"
          ]
        }
      },
      "response": []
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "exec": [
          "pm.variables.set("timestamp", new Date().toISOString());",
          "pm:variables.set("user_id", "postman-user-" + crypto.randomUUID());",
          "pm.variables.set("session_id", pm.variables.get("user_id") + ":web");",
          "pm.variables.set("conversation_id", "conv-" + crypto.randomUUID());",
          "pm.variables.set("trace_id", "trace-" + crypto.randomUUID());",
          "pm.variables.set("span_id", "span-" + crypto.randomUUID());",
          "pm.variables.set("message_id", "msg-" + crypto.randomUUID());"
        ],
        "type": "text/javascript"
      }
    }
  ],
  "variable": [
    {
      "key": "BASE_URL",
      "value": "https://<your-railway-url>",
      "type": "string"
    },
    {
      "key": "API_KEY",
      "value": "adapter-super-secret-key-1",
      "type": "string"
    }
  ]
}

铁路部署

这个项目是 铁路准备就绪:

  • Procfile 提供:
    web: uvicorn app:app --host 0.0.0.0 --port ${PORT:-8000}
    
  • 铁路将:
    • 从安装依赖项 requirements.txt
    • 检测Procfile
    • 公开指定的服务 $PORT

设置您的 MCP_URL 和 API_KEY / API_KEYS 在 铁路→ 变量.


项目结构

blinksbuy_mcp_adapter/
├── app.py           # FastAPI app with Canonical v1.1 adapter logic
├── README.md        # This documentation
├── requirements.txt # Python dependencies
├── Procfile         # Railway process definition
└── .env.example     # Example env vars (for local dev)

总结

Blinksbuy MCP适配器是:

  • 渠道无关
  • 多租户和多地区
  • LLM意识和多模式准备
  • 端到端可追溯
  • 专为现代AI自动化平台设计
  • 已准备好在上部署 铁路 开箱即用