使用说明
格子mcp
主控程序 服务器 格子状API,在Cloudflare Workers上运行。
尝试现场演示→ --无需凭据,包括沙盒测试帐户。
两台服务器:
- api服务器 -多租户Plaid API网关。每个客户端都使用自己的Plaid进行身份验证
client_id和secret通过Basic Auth,并获得一个与其自己的令牌库隔离的会话。 - 文档服务器 --Plaid文档搜索和检索。无需身份验证。
服务器
api服务器
将Plaid API操作公开为工具的可流式HTTP MCP服务器。部署地点:
https://plaid-api-mcp.myplaid.workers.dev/mcp
25工具 在这些类别中:
| 类别 | 工具 |
|---|---|
| 链接 | create_link_token, exchange_public_token |
| 沙箱 | sandbox_create_public_token, sandbox_reset_login, sandbox_fire_webhook |
| 项目 | get_item, remove_item |
| 账户 | get_accounts |
| 认证 | get_auth |
| 交易记录 | get_transactions_sync |
| 平衡 | get_balance |
| 身份 | get_identity |
| 投资 | get_investments_holdings, get_investments_transactions |
负债 get_liabilities |
|
| 转账 | create_transfer, get_transfer, list_transfers |
| 保险库 | list_items, remove_stored_item |
| 披露 | get_disclosure_settings, set_disclosure_level |
文档服务器
MCP服务器,用于搜索和阅读Plaid文档,由KV存储支持 llms.txt 内容。无需身份验证。部署地点:
https://plaid-docs-mcp.myplaid.workers.dev/mcp
工具: search_docs, get_doc_page, list_sections
身份验证(api服务器)
每个请求(CORS飞行前除外)都需要 Authorization: Basic <base64(client_id:secret)> 头球服务器本身不会验证凭据-如果凭据错误,Plaid API调用将失败,并显示Plaid自己的错误消息。文档服务器不需要身份验证。
保留凭证 仅在内存中。该框架对持久对象存储的默认道具持久性被覆盖,因此Plaid机密永远不会写入磁盘。每次请求都会通过Authorization标头收到新的凭据。
客户端配置
克劳德桌面/克劳德代码
{
"mcpServers": {
"plaid-api": {
"url": "https://plaid-api-mcp.myplaid.workers.dev/mcp",
"headers": {
"Authorization": "Basic <base64(client_id:secret)>"
}
},
"plaid-docs": {
"url": "https://plaid-docs-mcp.myplaid.workers.dev/mcp"
}
}
}
MCP SDK(TypeScript)
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
// API server (requires Plaid credentials)
const apiTransport = new StreamableHTTPClientTransport(
new URL("https://plaid-api-mcp.myplaid.workers.dev/mcp"),
{
requestInit: {
headers: {
Authorization: `Basic ${btoa(`${clientId}:${secret}`)}`,
},
},
},
);
const apiClient = new Client({ name: "my-agent", version: "1.0" });
await apiClient.connect(apiTransport);
// Docs server (no auth required)
const docsTransport = new StreamableHTTPClientTransport(
new URL("https://plaid-docs-mcp.myplaid.workers.dev/mcp"),
);
const docsClient = new Client({ name: "my-agent", version: "1.0" });
await docsClient.connect(docsTransport);
MCP SDK(Python)
from base64 import b64encode
from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
credentials = b64encode(f"{client_id}:{secret}".encode()).decode()
# API server (requires Plaid credentials)
async with streamablehttp_client(
"https://plaid-api-mcp.myplaid.workers.dev/mcp",
headers={"Authorization": f"Basic {credentials}"},
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
# Docs server (no auth required)
async with streamablehttp_client(
"https://plaid-docs-mcp.myplaid.workers.dev/mcp",
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
会话隔离
每个MCP会话都有自己的持久对象实例(由会话ID键控):
- 凭证 --每节课
PlaidClient使用该用户的client_id/secret,仅保存在内存中 - 代币库 --每个会话DO存储访问令牌。LLM只能看到不透明的
item_ref别名,从不原始访问令牌 - 披露偏好 --对数据详细级别(摘要与详细)的每会话控制
不可能发生跨租户数据泄漏——DO存储是针对每个实例的。
安全模型
API服务器处理多个租户之间的真实财务凭证和数据。安全设计遵循深度防御:多个独立层必须全部失效,敏感数据才会泄露。
1.通过持久对象隔离会话
每个MCP会话都映射到自己的会话 Cloudflare持久对象 实例,键入方式 mcp-session-id。每次会话都会得到:
- 它自己
PlaidClient使用该用户的凭据 - 它自己
TokenVault在单独的DO储存中 - 其自身的披露偏好
会话之间没有共享状态。持久对象存储是按实例设计的——一个会话无法读取另一个会话的保管库或凭据。
2.凭据从未写入磁盘
MCP框架(McpAgent)通常持续 props 到 ctx.storage 因此耐用对象可以从休眠中恢复。API服务器 覆盖两者 onStart() 和 updateProps() 要仅将凭据保存在内存中,请执行以下操作:
// workers/api-server/src/index.ts
async onStart(props: PlaidProps) {
if (props) this.props = props; // in-memory only
// ... framework wiring without storage.put()
}
async updateProps(props: PlaidProps) {
this.props = props; // in-memory only, no persistence
}
如果DO休眠,下一个HTTP请求将在 Authorization 标头,因此会话恢复时不需要存储机密。
3.代币库——不透明别名
当公共令牌被交换为访问令牌时。, "item-a1b2c3d4").LLM只会看到并引用这些别名。
关键设计决策 TokenVault:
store(alias, entry)--保存完整VaultEntry(包括access_token)DO存储resolve(alias)--仅返回access_token字符串,由工具在服务器端用于进行Plaid API调用list()--返回元数据 随着access_token明确剥离:
// workers/api-server/src/lib/token-vault.ts
async list(): Promise<Record<string, Omit<VaultEntry, "access_token">>> {
const entries = await this.storage.list<VaultEntry>({ prefix: KEY_PREFIX });
const result: Record<string, Omit<VaultEntry, "access_token">> = {};
for (const [key, entry] of entries) {
const alias = key.slice(KEY_PREFIX.length);
const { access_token: _, ...metadata } = entry; // strip token
result[alias] = metadata;
}
return result;
}
LLM可以询问“我有什么项目?”并返回别名和元数据,但永远不会返回原始令牌。
4.双重受众响应过滤
简单的API回复包含LLM并不总是需要查看完整的财务数据。服务器使用 MCP注释 向LLM和最终用户发送不同的内容:
// workers/api-server/src/lib/tool-helpers.ts
return {
content: [
{
type: "text",
text: summarize(data, level), // safe summary
annotations: { audience: ["assistant"] },
},
{
type: "text",
text: JSON.stringify(data, null, 2), // full response
annotations: { audience: ["user"] },
},
],
};
audience: ["assistant"]--LLM看到一个包含掩码帐号、编辑后的PII和汇总计数的摘要版本audience: ["user"]--客户端UI接收完整的JSON响应以供显示
这意味着LLM可以对数据进行推理(“您有3个支票账户”),而无需处理原始账号、SSN或路由号。
5.按类别数据汇总
每个数据类别都有自己的摘要功能,可以控制LLM看到的内容:
| 类别 | LLM查看(摘要) | LLM看到(详细) | 始终对LLM隐藏 |
|---|---|---|---|
| 账户 | 计数、姓名、蒙面数字(****1234) |
相同 | 完整帐号 |
| 认证 | 账户计数、ACH计数 | 相同 | 路由号、账号 |
| 平衡 | 账户名称、当前/可用余额 | 相同 | 完整账号 |
| 身份 | 所有者计数 | 仅限名字 | 全名、社会安全号码、地址、电话、电子邮件 |
| 交易 | 计数(添加/修改/删除) | 日期、商家、金额、类别 | 完整描述(摘要模式) |
| 投资 | 持有/证券计数 | 票据、数量、价值 | 成本基础、证券ID |
| 负债 | 类型计数(信用/学生/抵押贷款) | 余额、年利率、最低还款额 | 债权人详细信息、账号 |
6.用户控制的披露级别
用户可以通过两个工具控制LLM看到每个类别的详细程度:
get_disclosure_settings--显示当前级别set_disclosure_level--在以下之间更改类别"summary"和"detailed"
默认为 "summary" 适用于所有可配置的类别(交易、投资、负债、身份)。身份验证、帐户和余额是不可配置的——它们总是使用固定的摘要格式。
这为用户提供了明确的控制:“我希望LLM看到我的交易详细信息,这样它就可以帮助我制定预算”,而不是“告诉我计数”
7.输入验证
所有工具参数在执行前都会使用Zod模式进行验证。枚举类型将值限制为有效选项(例如,将网络传输到 ach | same-day-ach | wire).MCP框架在格式错误的输入到达工具处理程序之前拒绝它们。
摘要
| 层 | 它保护什么 | 机制 |
|---|---|---|
| 持久对象隔离 | 跨租户数据访问 | 每个会话单独的DO实例 |
| 内存中的凭据 | 静止的Plaid secrets | 覆盖持久性,唤醒时重新认证 |
| 令牌库别名 | 从LLM访问令牌 | 不透明引用, list() 剥离代币 |
| 双音频注释 | 来自LLM的完整API响应 | MCP audience 注释 |
| 类别摘要 | PII、帐号、SSN | 按类别 summarize() 功能 |
| 披露偏好 | 粒度数据暴露 | 用户控制的摘要与详细 |
| Zod验证 | 输入格式错误/注入 | 所有参数的模式验证 |
项目结构
plaid-mcp/
packages/shared/ # Shared types (PlaidEnv, DocEntry, etc.)
workers/api-server/ # Plaid API MCP server (Cloudflare Worker)
workers/docs-server/ # Plaid docs MCP server (Cloudflare Worker)
apps/playground/ # Web playground (Next.js) — chat UI with MCP tools
pnpm-onrepo。工人部署到Cloudflare,操场部署到Vercel。
发展
pnpm install
# Run locally
pnpm dev:api
pnpm dev:docs
# Type check
pnpm typecheck
# Deploy
pnpm deploy:api
pnpm deploy:docs
验证
# No credentials -> 401
curl -s -X POST https://plaid-api-mcp.myplaid.workers.dev/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}'
# With credentials -> MCP initialize response
curl -s -X POST https://plaid-api-mcp.myplaid.workers.dev/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Basic $(echo -n '<client_id>:<secret>' | base64)" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}'
