新增token
This commit is contained in:
+25
-13
@@ -1,10 +1,14 @@
|
|||||||
# 汽车零部件智能报价 — 两个 MCP 服务编排
|
# 汽车零部件智能报价 — 两个 MCP 服务编排
|
||||||
# 数据库为外部 PostgreSQL(192.168.1.206:5432),不在本编排内
|
# 生产环境:docker compose up -d
|
||||||
# 启动:docker compose up -d
|
# 默认连生产库 192.168.1.206:5432,对外 192.168.1.119:8001/8002
|
||||||
|
# 开发环境:本机直接 python server.py(不入容器)
|
||||||
|
# source .env.dev 后到 mcp-for-{erp,crm}/src 执行 python server.py
|
||||||
|
# 连开发库 47.101.220.40:5432,对外 localhost:8001/8002
|
||||||
|
# 也可用 docker 跑开发:docker compose --env-file .env.dev up -d
|
||||||
# 停止:docker compose down
|
# 停止:docker compose down
|
||||||
# 日志:docker compose logs -f
|
# 日志:docker compose logs -f
|
||||||
# 网络模式 host:容器直接共享宿主网络栈,绕开 Docker 桥接 FORWARD 链,
|
# 网络模式 host:容器直接共享宿主网络栈,绕开 Docker 桥接 FORWARD 链,
|
||||||
# 便于局域网其他机器经 192.168.1.119:8001/8002 直接访问(无需 ports 映射)
|
# 便于局域网其他机器直接访问(无需 ports 映射)
|
||||||
services:
|
services:
|
||||||
mcp-for-erp:
|
mcp-for-erp:
|
||||||
build:
|
build:
|
||||||
@@ -16,11 +20,15 @@ services:
|
|||||||
container_name: mcp-for-erp
|
container_name: mcp-for-erp
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
- ERP_DB_HOST=192.168.1.206
|
- ERP_DB_HOST=${ERP_DB_HOST:-192.168.1.206}
|
||||||
- ERP_DB_PORT=5432
|
- ERP_DB_PORT=${ERP_DB_PORT:-5432}
|
||||||
- ERP_DB_USER=postgres
|
- ERP_DB_USER=${ERP_DB_USER:-postgres}
|
||||||
- ERP_DB_PASSWORD=digiwin
|
- ERP_DB_PASSWORD=${ERP_DB_PASSWORD:-digiwin}
|
||||||
- ERP_DB_NAME=smart_quotation_auto
|
- ERP_DB_NAME=${ERP_DB_NAME:-smart_quotation_auto}
|
||||||
|
# 对外地址(用于 OAuth 资源元数据),按实际部署机 IP 调整
|
||||||
|
- MCP_PUBLIC_URL=${MCP_PUBLIC_URL_ERP:-http://192.168.1.119:8001}
|
||||||
|
# Bearer Token 表:token:client_id 逗号分隔,每个客户端一个独立 token
|
||||||
|
- MCP_AUTH_TOKENS=${MCP_AUTH_TOKENS_ERP:-erp-token-trae-2026:trae,erp-token-partner-a:partner-a}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
mcp-for-crm:
|
mcp-for-crm:
|
||||||
@@ -33,9 +41,13 @@ services:
|
|||||||
container_name: mcp-for-crm
|
container_name: mcp-for-crm
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
- CRM_DB_HOST=192.168.1.206
|
- CRM_DB_HOST=${CRM_DB_HOST:-192.168.1.206}
|
||||||
- CRM_DB_PORT=5432
|
- CRM_DB_PORT=${CRM_DB_PORT:-5432}
|
||||||
- CRM_DB_USER=postgres
|
- CRM_DB_USER=${CRM_DB_USER:-postgres}
|
||||||
- CRM_DB_PASSWORD=digiwin
|
- CRM_DB_PASSWORD=${CRM_DB_PASSWORD:-digiwin}
|
||||||
- CRM_DB_NAME=smart_quotation_auto
|
- CRM_DB_NAME=${CRM_DB_NAME:-smart_quotation_auto}
|
||||||
|
# 对外地址(用于 OAuth 资源元数据),按实际部署机 IP 调整
|
||||||
|
- MCP_PUBLIC_URL=${MCP_PUBLIC_URL_CRM:-http://192.168.1.119:8002}
|
||||||
|
# Bearer Token 表:token:client_id 逗号分隔,每个客户端一个独立 token
|
||||||
|
- MCP_AUTH_TOKENS=${MCP_AUTH_TOKENS_CRM:-crm-token-trae-2026:trae,crm-token-partner-a:partner-a}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""静态 Bearer Token 鉴权(Authorization)。
|
||||||
|
|
||||||
|
token → 客户端 映射来自环境变量 MCP_AUTH_TOKENS,格式(逗号分隔,每对 token:client_id):
|
||||||
|
MCP_AUTH_TOKENS=token-trae:trae,token-partner:partner-a
|
||||||
|
|
||||||
|
每个客户端配置自己的 token 调用,服务端校验失败返回 401;
|
||||||
|
工具内可通过 get_caller() 获取当前调用方标识。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from mcp.server.auth.middleware.auth_context import get_access_token
|
||||||
|
from mcp.server.auth.provider import AccessToken
|
||||||
|
from mcp.server.auth.settings import AuthSettings
|
||||||
|
|
||||||
|
|
||||||
|
class StaticTokenVerifier:
|
||||||
|
"""静态 token 查表校验:命中返回 AccessToken(client_id 即客户端标识),未命中返回 None(401)。"""
|
||||||
|
|
||||||
|
def __init__(self, tokens: dict[str, str]):
|
||||||
|
self._tokens = tokens
|
||||||
|
|
||||||
|
async def verify_token(self, token: str) -> AccessToken | None:
|
||||||
|
client_id = self._tokens.get(token)
|
||||||
|
if client_id is None:
|
||||||
|
return None
|
||||||
|
return AccessToken(token=token, client_id=client_id, scopes=[], expires_at=None)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_tokens(raw: str) -> dict[str, str]:
|
||||||
|
"""解析 'token1:client1,token2:client2' → {token: client_id}"""
|
||||||
|
tokens: dict[str, str] = {}
|
||||||
|
for item in raw.split(","):
|
||||||
|
item = item.strip()
|
||||||
|
if not item:
|
||||||
|
continue
|
||||||
|
token, _, client_id = item.partition(":")
|
||||||
|
token, client_id = token.strip(), client_id.strip()
|
||||||
|
if token and client_id:
|
||||||
|
tokens[token] = client_id
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
|
def get_auth(port: int) -> tuple[AuthSettings, StaticTokenVerifier]:
|
||||||
|
"""构建 MCPServer 的 (auth, token_verifier) 参数。
|
||||||
|
|
||||||
|
服务对外地址默认 http://localhost:{port},部署时用 MCP_PUBLIC_URL 覆盖
|
||||||
|
(如 http://192.168.1.119:8002),用于 OAuth 资源元数据发现。
|
||||||
|
"""
|
||||||
|
url = os.getenv("MCP_PUBLIC_URL") or f"http://localhost:{port}"
|
||||||
|
tokens = _parse_tokens(os.getenv("MCP_AUTH_TOKENS", ""))
|
||||||
|
if not tokens:
|
||||||
|
raise RuntimeError("环境变量 MCP_AUTH_TOKENS 未配置,格式:token1:client1,token2:client2")
|
||||||
|
return (
|
||||||
|
AuthSettings(issuer_url=url, resource_server_url=url),
|
||||||
|
StaticTokenVerifier(tokens),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_caller() -> str:
|
||||||
|
"""工具内获取当前调用方标识(未认证时返回 anonymous)。"""
|
||||||
|
access_token = get_access_token()
|
||||||
|
return access_token.client_id if access_token else "anonymous"
|
||||||
@@ -8,6 +8,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from mcp.server import MCPServer
|
from mcp.server import MCPServer
|
||||||
|
from auth import get_auth
|
||||||
from db import get_pool
|
from db import get_pool
|
||||||
|
|
||||||
|
|
||||||
@@ -23,10 +24,15 @@ def _s(value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# 鉴权:静态 Bearer Token,token → 客户端 映射来自环境变量 MCP_AUTH_TOKENS
|
||||||
|
_auth, _token_verifier = get_auth(port=8002)
|
||||||
|
|
||||||
app = MCPServer(
|
app = MCPServer(
|
||||||
name="mcp-for-crm-auto",
|
name="mcp-for-crm-auto",
|
||||||
description="汽车零部件智能报价 CRM 数据服务",
|
description="汽车零部件智能报价 CRM 数据服务",
|
||||||
version="1.0.0"
|
version="1.0.0",
|
||||||
|
auth=_auth,
|
||||||
|
token_verifier=_token_verifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""静态 Bearer Token 鉴权(Authorization)。
|
||||||
|
|
||||||
|
token → 客户端 映射来自环境变量 MCP_AUTH_TOKENS,格式(逗号分隔,每对 token:client_id):
|
||||||
|
MCP_AUTH_TOKENS=token-trae:trae,token-partner:partner-a
|
||||||
|
|
||||||
|
每个客户端配置自己的 token 调用,服务端校验失败返回 401;
|
||||||
|
工具内可通过 get_caller() 获取当前调用方标识。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from mcp.server.auth.middleware.auth_context import get_access_token
|
||||||
|
from mcp.server.auth.provider import AccessToken
|
||||||
|
from mcp.server.auth.settings import AuthSettings
|
||||||
|
|
||||||
|
|
||||||
|
class StaticTokenVerifier:
|
||||||
|
"""静态 token 查表校验:命中返回 AccessToken(client_id 即客户端标识),未命中返回 None(401)。"""
|
||||||
|
|
||||||
|
def __init__(self, tokens: dict[str, str]):
|
||||||
|
self._tokens = tokens
|
||||||
|
|
||||||
|
async def verify_token(self, token: str) -> AccessToken | None:
|
||||||
|
client_id = self._tokens.get(token)
|
||||||
|
if client_id is None:
|
||||||
|
return None
|
||||||
|
return AccessToken(token=token, client_id=client_id, scopes=[], expires_at=None)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_tokens(raw: str) -> dict[str, str]:
|
||||||
|
"""解析 'token1:client1,token2:client2' → {token: client_id}"""
|
||||||
|
tokens: dict[str, str] = {}
|
||||||
|
for item in raw.split(","):
|
||||||
|
item = item.strip()
|
||||||
|
if not item:
|
||||||
|
continue
|
||||||
|
token, _, client_id = item.partition(":")
|
||||||
|
token, client_id = token.strip(), client_id.strip()
|
||||||
|
if token and client_id:
|
||||||
|
tokens[token] = client_id
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
|
def get_auth(port: int) -> tuple[AuthSettings, StaticTokenVerifier]:
|
||||||
|
"""构建 MCPServer 的 (auth, token_verifier) 参数。
|
||||||
|
|
||||||
|
服务对外地址默认 http://localhost:{port},部署时用 MCP_PUBLIC_URL 覆盖
|
||||||
|
(如 http://192.168.1.119:8002),用于 OAuth 资源元数据发现。
|
||||||
|
"""
|
||||||
|
url = os.getenv("MCP_PUBLIC_URL") or f"http://localhost:{port}"
|
||||||
|
tokens = _parse_tokens(os.getenv("MCP_AUTH_TOKENS", ""))
|
||||||
|
if not tokens:
|
||||||
|
raise RuntimeError("环境变量 MCP_AUTH_TOKENS 未配置,格式:token1:client1,token2:client2")
|
||||||
|
return (
|
||||||
|
AuthSettings(issuer_url=url, resource_server_url=url),
|
||||||
|
StaticTokenVerifier(tokens),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_caller() -> str:
|
||||||
|
"""工具内获取当前调用方标识(未认证时返回 anonymous)。"""
|
||||||
|
access_token = get_access_token()
|
||||||
|
return access_token.client_id if access_token else "anonymous"
|
||||||
@@ -7,6 +7,7 @@ HTTP 模式,支持远程调用
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from mcp.server import MCPServer
|
from mcp.server import MCPServer
|
||||||
|
from auth import get_auth
|
||||||
from db import get_pool
|
from db import get_pool
|
||||||
|
|
||||||
|
|
||||||
@@ -22,10 +23,15 @@ def _s(value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# 鉴权:静态 Bearer Token,token → 客户端 映射来自环境变量 MCP_AUTH_TOKENS
|
||||||
|
_auth, _token_verifier = get_auth(port=8001)
|
||||||
|
|
||||||
app = MCPServer(
|
app = MCPServer(
|
||||||
name="mcp-for-erp-auto",
|
name="mcp-for-erp-auto",
|
||||||
description="汽车零部件智能报价 ERP 数据服务",
|
description="汽车零部件智能报价 ERP 数据服务",
|
||||||
version="1.0.0"
|
version="1.0.0",
|
||||||
|
auth=_auth,
|
||||||
|
token_verifier=_token_verifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user