Skip to main content
Glama
ivantagesam

OpsBridge MCP

by ivantagesam

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| Audit
src/
  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。

暴露的工具

工具

类型

用途

search_customers

读取

按姓名或电子邮件查找客户(部分匹配,不区分大小写)

get_customer

读取

按ID获取单个客户的详细信息

list_customer_tickets

读取

列出客户的工单,可选按状态筛选

create_support_ticket

写入

创建新工单——需要显式approved: true

由SQLite支持,使用合成、虚构数据:10个客户,18个种子支持工单。

技术栈

层

选择

原因

语言

TypeScript,严格模式 + noUncheckedIndexedAccess / exactOptionalPropertyTypes

捕获此项目关注的层边界处的真实错误(可选字段、索引访问)

MCP SDK

@modelcontextprotocol/sdk 1.30.0

当前发布的主要版本——截至撰写时没有v2;根据安装包自身的.d.ts文件验证,而非教程

模式验证

zod ^4

运行时验证和发送给客户端的JSON Schema的单一事实来源

数据库

better-sqlite3 ^12(同步)

对于单进程本地服务器,无需异步驱动/连接池复杂性;使用^12而非更新的13.x,因为13.x需要Node 22+,而此项目目标为Node 20+

运行时

Node.js 20+

声明的项目基线

测试

vitest ^4

通过InMemoryTransport将真实的MCP Client连接到真实的McpServer——参见测试

代码检查

eslint ^10 + typescript-eslint ^8

typescript-eslint尚不支持TypeScript 7(新的基于Go的编译器),因此TypeScript被固定为5.9.x系列——这是刻意的兼容性选择,而非疏忽

开发运行器

tsx

在开发期间无需构建步骤即可直接运行src/index.ts

审批机制

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
}

两件事使其成为实际的强制机制而非建议:

  1. 它在域层运行,位于MCP工具层之下,在任何SQL执行之前——从工具处理器到数据库INSERT不存在跳过它的代码路径。

  2. 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前要求你确认,要么使用approved false/省略调用一次,被拒绝,并将提议的工单返回给你。无论哪种方式,在您实际同意且模型再次以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,而不仅仅是在本地按需运行。

这里没有实现任何这些 —— 这个项目的意义是以小规模正确演示该模式,而不是预先构建真实部署需要但演示不需要的基础设施。

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    A 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.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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