plaid-mcp

plaid-mcp

MCP servers for the Plaid API, providing tools to access Plaid financial data and documentation. Includes an authenticated API server for account, transaction, and investment operations, and an unauthenticated docs server for searching Plaid documentation.

Category
访问服务器

README

plaid-mcp

MCP servers for the Plaid API, running on Cloudflare Workers.

Try the live demo → — no credentials needed, sandbox test accounts included.

Two servers:

  • api-server — Multi-tenant Plaid API gateway. Each client authenticates with their own Plaid client_id and secret via Basic Auth and gets an isolated session with its own token vault.
  • docs-server — Plaid documentation search and retrieval. No authentication required.

Servers

api-server

Streamable HTTP MCP server exposing Plaid API operations as tools. Deployed at:

https://plaid-api-mcp.myplaid.workers.dev/mcp

25 tools across these categories:

Category Tools
Link create_link_token, exchange_public_token
Sandbox sandbox_create_public_token, sandbox_reset_login, sandbox_fire_webhook
Items get_item, remove_item
Accounts get_accounts
Auth get_auth
Transactions get_transactions_sync
Balance get_balance
Identity get_identity
Investments get_investments_holdings, get_investments_transactions
Liabilities get_liabilities
Transfers create_transfer, get_transfer, list_transfers
Vault list_items, remove_stored_item
Disclosure get_disclosure_settings, set_disclosure_level

docs-server

MCP server for searching and reading Plaid documentation, backed by KV-stored llms.txt content. No authentication required. Deployed at:

https://plaid-docs-mcp.myplaid.workers.dev/mcp

Tools: search_docs, get_doc_page, list_sections

Authentication (api-server)

Every request (except CORS preflight) requires an Authorization: Basic <base64(client_id:secret)> header. The server does not validate credentials itself — if they're wrong, Plaid API calls fail with Plaid's own error messages. The docs-server requires no authentication.

Credentials are kept in-memory only. The framework's default prop persistence to Durable Object storage is overridden so that Plaid secrets are never written to disk. Fresh credentials arrive on every request via the Authorization header.

Client Configuration

Claude Desktop / Claude Code

{
  "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()

Session Isolation

Each MCP session gets its own Durable Object instance (keyed by session ID):

  • Credentials — per-session PlaidClient using that user's client_id/secret, held in memory only
  • Token vault — per-session DO storage for access tokens. The LLM only sees opaque item_ref aliases, never raw access tokens
  • Disclosure preferences — per-session controls over data detail levels (summary vs. detailed)

No cross-tenant data leakage is possible — DO storage is per-instance.

Security Model

The API server handles real financial credentials and data across multiple tenants. The security design follows defense-in-depth: multiple independent layers must all fail for sensitive data to leak.

1. Session Isolation via Durable Objects

Each MCP session maps to its own Cloudflare Durable Object instance, keyed by mcp-session-id. Every session gets:

  • Its own PlaidClient with that user's credentials
  • Its own TokenVault in isolated DO storage
  • Its own disclosure preferences

There is no shared state between sessions. Durable Object storage is per-instance by design — one session cannot read another's vault or credentials.

2. Credentials Never Written to Disk

The MCP framework (McpAgent) normally persists props to ctx.storage so Durable Objects can recover from hibernation. The API server overrides both onStart() and updateProps() to keep credentials in memory only:

// 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
}

If the DO hibernates, the next HTTP request carries fresh credentials in the Authorization header, so the session recovers without needing stored secrets.

3. Token Vault — Opaque Aliases

When a public token is exchanged for an access token, the raw token is stored in the vault under an opaque alias (e.g., "item-a1b2c3d4"). The LLM only ever sees and references these aliases.

Key design decisions in TokenVault:

  • store(alias, entry) — saves the full VaultEntry (including access_token) to DO storage
  • resolve(alias) — returns only the access_token string, used server-side by tools to make Plaid API calls
  • list() — returns metadata with access_token explicitly stripped:
// 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;
}

The LLM can ask "what items do I have?" and gets back aliases and metadata, but never a raw token.

4. Dual-Audience Response Filtering

Plaid API responses contain financial data that the LLM doesn't always need to see in full. The server uses MCP annotations to send different content to the LLM vs. the end user:

// 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"] — the LLM sees a summarized version with masked account numbers, redacted PII, and aggregated counts
  • audience: ["user"] — the client UI receives the full JSON response for display

This means the LLM can reason about the data ("you have 3 checking accounts") without processing raw account numbers, SSNs, or routing numbers.

5. Per-Category Data Summarization

Each data category has its own summary function that controls what the LLM sees:

Category LLM Sees (Summary) LLM Sees (Detailed) Always Hidden from LLM
Accounts Count, names, masked numbers (****1234) Same Full account numbers
Auth Account count, ACH count Same Routing numbers, account numbers
Balance Account names, current/available balances Same Full account numbers
Identity Owner count First names only Full names, SSNs, addresses, phones, emails
Transactions Counts (added/modified/removed) Date, merchant, amount, category Full descriptions (in summary mode)
Investments Holding/security counts Ticker, quantity, value Cost basis, security IDs
Liabilities Type counts (credit/student/mortgage) Balances, APR, min payments Creditor details, account numbers

6. User-Controlled Disclosure Levels

Users can control how much detail the LLM sees per category via two tools:

  • get_disclosure_settings — shows current levels
  • set_disclosure_level — changes a category between "summary" and "detailed"

Defaults to "summary" for all configurable categories (transactions, investments, liabilities, identity). Auth, accounts, and balance are not configurable — they always use their fixed summary format.

This gives users explicit control: "I want the LLM to see my transaction details so it can help me budget" vs. "just tell me the count."

7. Input Validation

All tool parameters are validated with Zod schemas before execution. Enum types restrict values to valid options (e.g., transfer networks to ach | same-day-ach | wire). The MCP framework rejects malformed inputs before they reach tool handlers.

Summary

Layer What It Protects Mechanism
Durable Object isolation Cross-tenant data access Separate DO instance per session
In-memory credentials Plaid secrets at rest Override persistence, re-auth on wake
Token vault aliases Access tokens from LLM Opaque refs, list() strips tokens
Dual-audience annotations Full API responses from LLM MCP audience annotations
Category summarization PII, account numbers, SSNs Per-category summarize() functions
Disclosure preferences Granular data exposure User-controlled summary vs. detailed
Zod validation Malformed/injected inputs Schema validation on all parameters

Project Structure

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 monorepo. Workers deploy to Cloudflare, playground deploys to Vercel.

Development

pnpm install

# Run locally
pnpm dev:api
pnpm dev:docs

# Type check
pnpm typecheck

# Deploy
pnpm deploy:api
pnpm deploy:docs

Verification

# 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"}}}'

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选