OpsBridge MCP
OpsBridge MCP
一个模型上下文协议(MCP)服务器,为AI客户端提供对业务客户及支持工单数据的受控、可审计访问——包括一个真实的写入操作,该操作由服务器强制执行的审批检查门控,而非提示指令。
这是一个聚焦的技术演示,而非产品。 它是一个作品集项目,旨在做好一件事:在TypeScript中正确实现一个MCP服务器,并具备将仅能运行的演示与真正可安全指向LLM的演示区分开来的特定工程纪律——模式验证、参数化SQL、在应用代码中强制执行的审批门,以及审计跟踪,全部对照真实SDK和真实协议验证,而非假设。它未部署在任何地方,没有真实客户,也不声称生产就绪——请参阅限制和生产环境我会做的改动以了解确切界限。
这解决了什么问题
AI客户端日益被期望对真实系统采取真实行动,而不仅仅是回答问题。这产生了一个具体的工程问题:如何让模型读取实时业务数据并执行一项有后果的操作,而不会(a)给予其不受限制的数据库访问权限,或(b)信任提示是“模型建议了此操作”与“此操作实际发生”之间的唯一屏障?
OpsBridge是对该问题在一个具体场景下的小而完整的回答:一个支持工单系统。它精确暴露了AI助手所需的数据(客户、工单),以及恰好一种更改任何内容的方式(创建工单)——而该写入路径无法执行,除非调用者明确提供approved: true,该检查在服务器代码中运行,无论模型“决定”什么。项目中的其他一切——模式、错误处理、审计日志——都旨在使这一保证真正可信。
Related MCP server: Customer Support MCP Server
MCP在此架构中的作用
Model Context Protocol是使AI客户端(Claude Code、Claude Desktop、MCP Inspector或任何其他支持MCP的客户端)能够发现此服务器可执行的操作并调用它,而无需为每个客户端编写自定义集成代码的层。具体而言,在此项目中,MCP负责:
工具发现——服务器通告
search_customers、get_customer、list_customer_tickets和create_support_ticket,每个工具都有JSON-Schema描述的输入和输出,由本项目的Zod模式自动生成。结构化请求/响应契约——每次工具调用在项目代码运行前都会根据其模式进行验证,每个响应要么是正常结果,要么是格式良好的
isError: true结果——绝不会是原始异常或格式错误的回复。传输——通过stdio的JSON-RPC 2.0。客户端将
node dist/index.js作为子进程启动,并通过stdin/stdout与其通信;没有网络端口。
MCP不执行任何实际工作——它是通用AI客户端无需定制胶水代码即可使用此服务器的原因。业务逻辑、验证和安全保证是本项目自身的。
架构
flowchart TD
Client["Claude Code / MCP Client"]
Protocol["MCP Protocol<br/>(JSON-RPC over stdio)"]
Server["OpsBridge MCP Server<br/>src/server.ts · src/index.ts"]
Tools["Tool Layer<br/>src/tools/*.ts"]
Approval["Approval / Validation<br/>src/domain/*.ts"]
DB[("SQLite Database<br/>src/db/*.ts")]
Audit["Audit Log (stderr)<br/>src/lib/audit.ts"]
Client --> Protocol --> Server --> Tools --> Approval --> DB
Tools -.->|every call, success or failure| Auditsrc/
db/ SQLite schema, synthetic seed data, idempotent seeding
domain/ Repository functions (customers, tickets) — plain TS, no MCP knowledge
tools/ One file per MCP tool: Zod schema, audit-log wrapper, thin handler
lib/ Audit logging (lib/audit.ts) and typed error classes (lib/errors.ts)
server.ts Builds the McpServer and registers all tools
index.ts Entrypoint — opens/seeds the DB, connects stdio transport分层是有意且单向的:每层仅了解其下一层,domain/不导入@modelcontextprotocol/sdk中的任何内容——它是基于better-sqlite3数据库的纯TypeScript。这使得测试套件能够测试真实的端到端工具调用路径(真实的MCP Client与真实的McpServer通信),而不是模拟层边界。完整说明,包括精确代码路径:docs/architecture.md。
暴露的工具
工具 | 类型 | 用途 |
| 读取 | 按姓名或电子邮件查找客户(部分匹配,不区分大小写) |
| 读取 | 按ID获取单个客户的详细信息 |
| 读取 | 列出客户的工单,可选按状态筛选 |
| 写入 | 创建新工单——需要显式 |
由SQLite支持,使用合成、虚构数据:10个客户,18个种子支持工单。
技术栈
层 | 选择 | 原因 |
语言 | TypeScript,严格模式 + | 捕获此项目关注的层边界处的真实错误(可选字段、索引访问) |
MCP SDK |
| 当前发布的主要版本——截至撰写时没有v2;根据安装包自身的 |
模式验证 |
| 运行时验证和发送给客户端的JSON Schema的单一事实来源 |
数据库 |
| 对于单进程本地服务器,无需异步驱动/连接池复杂性;使用 |
运行时 | Node.js 20+ | 声明的项目基线 |
测试 |
| 通过 |
代码检查 |
|
|
开发运行器 |
| 在开发期间无需构建步骤即可直接运行 |
审批机制
create_support_ticket是系统中唯一有后果的操作,因此它是本项目添加硬门的地方:
// src/domain/tickets.ts
export function createSupportTicket(db, input: CreateTicketInput): Ticket {
if (input.approved !== true) {
throw new ApprovalRequiredError(
"Ticket creation was not approved. Set approved=true to confirm this action before it is created.",
);
}
// ... only reaches the INSERT after this point
}两件事使其成为实际的强制机制而非建议:
它在域层运行,位于MCP工具层之下,在任何SQL执行之前——从工具处理器到数据库
INSERT不存在跳过它的代码路径。approved是工具输入模式中的必需布尔值,而非可选。省略它,调用会在代码运行前因模式验证失败;传递false,则在此处被拒绝。
工具描述还要求模型先与用户确认——但这是模型行为的建议性文本,而非系统安全的原因。即使模型忽略描述并直接调用工具,该保证仍然成立;服务器而非提示是最后一道防线。
这不保证的是: 人类实际设置了该标志——approved: true只是模型可能自行提供的另一个参数,而人类从未看到该请求。完全弥合这一差距需要服务器强制交互式确认往返回人类(MCP elicitation);本项目有意不添加此功能,因为对于本项目不声称提供的保证而言,这是一个真实的交互模型更改。参见限制。
安全考虑
审批在应用程序代码中强制执行,而非提示——参见上文。
每次工具调用都会审计记录到stderr(
src/lib/audit.ts,通过withAudit()包装器应用于所有四个工具):工具名称、时间戳、成功/失败,以及非敏感标识符(适用时为customer_id);create_support_ticket行还记录调用是否被批准。绝不记录调用的敏感内容——不记录工单主题/描述、原始搜索查询文本、电子邮件/电话/姓名。所有SQL均通过
better-sqlite3预处理语句参数化——无字符串拼接,因此即使输入最终来自LLM,也不存在SQL注入面。search_customers的LIKE模式还转义了%/_,使搜索文本按字面匹配,而非作为通配符(否则仅查询"%"将返回每一行)。输入在到达任何业务逻辑之前使用Zod验证——长度限制、
priority/status的枚举约束——以清晰的错误拒绝格式错误的输入,而非将其传递。存储的工单文本被框架为数据,而非指令。
subject/description是自由文本,现在创建的工单会被后续的list_customer_tickets调用逐字读取——这是一个二阶提示注入向量。响应文本明确指出此内容是存储的客户输入,而非指令。这是一种缓解措施,而非保证。无身份验证或授权。 这是一个本地、单用户演示——任何能启动该进程的人都能完全访问每个工具,包括完整的客户PII。此处明确超出范围;在接触真实、多租户数据之前必须更改。
项目中无任何秘密。 无API密钥、令牌或凭据;唯一的外部依赖是本地SQLite文件,该文件已被gitignore。
示例Claude交互
连接后的读取路径提示:
“搜索名为Chen的客户。”
“获取客户cust_004的完整详细信息。”
“cust_005有哪些未关闭的工单?”
有趣的是写入路径:
你: “为cust_002创建高优先级支持工单,关于其跟踪号码未同步——但在实际创建前先与我确认。”
预期行为: 模型按需调用
search_customers/get_customer,然后要么在调用create_support_ticket前要求你确认,要么使用approvedfalse/省略调用一次,被拒绝,并将提议的工单返回给你。无论哪种方式,在您实际同意且模型再次以approved: true调用之前,不会写入任何内容。
更多脚本化演练,包括强制拒绝路径以直接查看原始强制消息:docs/demo-script.md。
本地设置
需要Node.js 20+。
npm install
npm run db:seed # creates and seeds data/opsbridge.db (10 customers, 18 tickets)
npm run build # compiles TypeScript to dist/npm run dev # runs src/index.ts directly with tsx (auto-seeds on first run)
# or, after `npm run build`:
npm start # runs dist/index.js服务器通过stdio通信——无HTTP端口,无法直接浏览。
连接到 Claude Code: 本仓库包含一个项目范围的 .mcp.json(通过 claude mcp add opsbridge --scope project -- node dist/index.js 生成,因此它正是 CLI 自身产出的内容,而非手写的)。先构建,然后批准一次:
npm run build
claude # prompts to trust this project's .mcp.json server on first run — approve it
claude mcp list # should show: opsbridge: node dist/index.js - ✔ Connected连接任何其他 MCP 客户端(Claude Desktop 等)——大多数读取带有 command/args 对的 JSON 配置:
{
"mcpServers": {
"opsbridge": {
"command": "node",
"args": ["/absolute/path/to/opsbridge-mcp/dist/index.js"]
}
}
}在没有完整客户端的情况下手动测试 —— MCP Inspector,版本被刻意固定(未指定版本的 npx @modelcontextprotocol/inspector 可能会解析到过时的缓存构建,而不是当前版本):
npx @modelcontextprotocol/inspector@2.3.0 node dist/index.js # web UI
npx @modelcontextprotocol/inspector@2.3.0 --cli node dist/index.js -- --method tools/list # headless测试
npm test # vitest — 33 tests across 6 files
npm run typecheck
npm run lint测试通过 SDK 的 InMemoryTransport 将真实的 MCP Client 连接到真实的 McpServer,每个测试使用全新的内存 SQLite 数据库(tests/helpers.ts)——实际演练了真实客户端所经历的请求 → Zod 验证 → 工具处理器 → 响应路径,而不仅仅是孤立地测试领域函数。覆盖范围包括:成功和空结果的搜索、客户未找到、带/不带状态筛选的工单列表、每个工具的无效输入、以 approved: false 和完全省略 approved 两种方式被拒绝的工单创建、成功创建、重复提交安全性、LIKE 通配符转义、提示注入框架文本,以及每个工具的审计日志内容(包括 PII 永远不会出现在日志行中)。
限制
为聚焦演示而刻意裁剪的范围,而非疏忽:
没有身份验证、授权或按用户的数据范围划分 —— 参见安全考虑。
审批标志不是经过验证的人类信号 —— 它是一个模型可以自行设置的布尔值;请参阅批准机制。
无分页 —— 搜索最多返回 10 条结果;工单列表无上限,但数据集很小。
没有更新或删除工具 —— 只有工单创建是写操作。
仅 stdio 传输 —— 没有 HTTP/SSE,没有远程部署方案。
create_support_ticket上没有速率限制或幂等键 —— 重试的调用会创建第二个独立的工单,而不是被去重。SQLite,单进程 —— 没有连接池,除了
CREATE TABLE IF NOT EXISTS之外没有迁移工具。审计日志是本地 stderr 流 —— 不发送到任何地方,不可查询,没有保留策略。
生产环境中我会做的改动
如果这个模式曾真正指向真实客户而不是合成演示数据:
从 stdio 迁移到支持 OAuth bearer 认证的 Streamable HTTP,按租户/客户划分范围 —— SDK 已经支持这种传输;当前的 stdio 模型隐式信任任何能启动该进程的人,这仅适用于本地演示,其他地方都不行。
添加真正的授权,将经过认证的调用者映射到他们可以访问的客户/工单 —— 目前每个工具都是无范围的。
让审批可验证,而不仅仅是存在 —— 使用 MCP 引导来强制向人类进行真实的往返确认,或要求由模型控制之外的单独确认步骤铸造的短期令牌。
将 SQLite 替换为 Postgres,使用连接池和真正的迁移工具。
将审计日志发送到持久且可查询的地方(而不是 stderr),并配备与其审计内容相适应的保留和访问控制。
在写入路径上添加速率限制和幂等键。
为
search_customers和list_customer_tickets添加分页。添加可观测性 —— 每个工具的延迟、错误率和调用量。
在 CI 中对每次更改运行类型检查/测试/lint,而不仅仅是在本地按需运行。
这里没有实现任何这些 —— 这个项目的意义是以小规模正确演示该模式,而不是预先构建真实部署需要但演示不需要的基础设施。
This server cannot be deployed
Maintenance
Related MCP Connectors
Runtime permission, approval, and audit layer for AI agent tool execution.
Supervised API-write gateway for AI agents with policy, human approval and execution receipts.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Guard AI agents' PostgreSQL/MySQL access via MCP: SQL audit, auth, masking, write approval
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceA secure MCP server that exposes a SQLite database to AI agents with Role-Based Access Control, supporting authentication, customer/order/user management, and audit logging.-
- FlicenseAqualityCmaintenanceEnables AI assistants like Cursor to manage customer support tickets in SQLite through MCP tools, supporting creation, retrieval, search, and updates via natural language.4-
- AlicenseNot gradedqualityCmaintenanceEnables interaction with a local SQLite-backed issue tracker, offering full CRUD operations (search, fetch, summarize, create, comment, close/reopen) with team-scoped visibility, authorization, rate limiting, and audit logging.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to safely work with SQLite databases by enforcing read/write separation, dry-run writes with confirmation, automatic backups, and an audit trail.MIT