OpsBridge MCP
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@OpsBridge MCPSearch for customers named Acme and list their open tickets"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OpsBridge MCP
A Model Context Protocol (MCP) server that gives an AI client controlled, auditable access to a business's customer and support-ticket data — including one real write action, gated by a server-enforced approval check rather than a prompt instruction.
This is a focused technical demonstration, not a product. It's a portfolio piece built to show one thing well: a correctly-implemented MCP server in TypeScript, with the specific engineering discipline that separates a demo that merely works from one that's actually safe to point an LLM at — schema validation, parameterized SQL, an approval gate enforced in application code, and an audit trail, all verified against the real SDK and the real protocol rather than assumed. It is not deployed anywhere, has no real customers, and is not claiming production readiness — see Limitations and What I'd change for production for exactly where that line is.
What problem this solves
AI clients are increasingly expected to take real actions on real systems, not just answer questions. That creates a specific engineering problem: how do you let a model read live business data and perform a consequential action, without either (a) giving it unrestricted database access, or (b) trusting the prompt to be the only thing standing between "the model suggested this" and "this actually happened"?
OpsBridge is a small, complete answer to that problem for one concrete case: a support-ticket
system. It exposes exactly the data an AI assistant needs (customers, tickets), and exactly one way
to change anything (create a ticket) — and that one write path cannot execute unless the caller
explicitly supplies approved: true, checked in server code that runs regardless of what the model
"decides." Everything else in the project — schemas, error handling, audit logging — exists to make
that one guarantee actually trustworthy.
Related MCP server: SQLite MCP Server
What MCP is doing in this architecture
The Model Context Protocol is the layer that lets an AI client (Claude Code, Claude Desktop, the MCP Inspector, or anything else that speaks MCP) discover what this server can do and call it, without any custom integration code per client. Concretely, in this project MCP is responsible for:
Tool discovery — the server advertises
search_customers,get_customer,list_customer_tickets, andcreate_support_ticket, each with a JSON-Schema-described input and output, generated automatically from this project's Zod schemas.A structured request/response contract — every tool call is validated against its schema before this project's code ever runs, and every response is either a normal result or a well-formed
isError: trueresult — never a raw exception or a malformed reply.Transport — JSON-RPC 2.0 over stdio. The client spawns
node dist/index.jsas a subprocess and talks to it over stdin/stdout; there's no network port.
MCP does not do any of the actual work — it's the reason a generic AI client can use this server at all without bespoke glue code. The business logic, validation, and safety guarantees are this project's own.
Architecture
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 transportThe layering is deliberate and one-directional: each layer only knows about the one below it, and
domain/ has no import of anything from @modelcontextprotocol/sdk — it's plain TypeScript
operating on a better-sqlite3 database. That's what lets the test suite exercise the real,
end-to-end tool-call path (a real MCP Client talking to a real McpServer) instead of mocking
the layer boundaries. Full write-up, including exact code paths: docs/architecture.md.
Tools exposed
Tool | Type | Purpose |
| read | Find customers by name or email (partial, case-insensitive) |
| read | Fetch one customer's details by id |
| read | List a customer's tickets, optionally filtered by status |
| write | Create a new ticket — requires explicit |
Backed by SQLite with synthetic, fictional data: 10 customers, 18 seeded support tickets.
Technology stack
Layer | Choice | Why |
Language | TypeScript, strict mode + | Catches real bugs at the layer boundaries this project cares about (optional fields, indexed access) |
MCP SDK |
| Current published major version — there is no v2 as of this writing; verified against the installed package's own |
Schema validation |
| Single source of truth for both runtime validation and the JSON Schema sent to clients |
Database |
| No async driver/pool complexity for a single-process local server; |
Runtime | Node.js 20+ | Stated project baseline |
Tests |
| Connects a real MCP |
Lint |
|
|
Dev runner |
| Runs |
Approval mechanism
create_support_ticket is the one consequential action in the system, so it's the one place this
project adds a hard gate:
// 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
}Two things make this an actual enforcement mechanism rather than a suggestion:
It runs in the domain layer, below the MCP tool layer, before any SQL executes — there is no code path from the tool handler to the database
INSERTthat skips it.approvedis a required boolean in the tool's input schema, not optional. Omit it and the call fails schema validation before this code even runs; passfalseand it's rejected here.
The tool description also asks the model to confirm with the user first — but that's advisory text for the model's behavior, not what makes the system safe. The guarantee holds even if a model ignores the description and calls the tool directly; the server, not the prompt, is the last line of defense.
What this does not guarantee: that a human actually set the flag — approved: true is just
another argument a model could supply on its own initiative, with no human ever seeing the request.
Closing that gap fully would require the server to force an interactive confirmation round-trip
back to a human (MCP elicitation); this project deliberately doesn't add that, since it's a real
interaction-model change for a guarantee this project doesn't claim to provide. See
Limitations.
Security considerations
Approval is enforced in application code, not the prompt — see above.
Every tool call is audit-logged to stderr (
src/lib/audit.ts, applied at the tool layer via awithAudit()wrapper around all four tools): tool name, timestamp, success/failure, and a non-sensitive identifier (customer_idwhere applicable);create_support_ticketlines also record whether the call was approved. Never the sensitive content of a call — no ticket subjects/descriptions, no raw search query text, no email/phone/name.All SQL is parameterized via
better-sqlite3prepared statements — no string concatenation, so there's no SQL injection surface even though input ultimately originates from an LLM.search_customers'LIKEpattern also escapes%/_so search text is matched literally, not as a wildcard (otherwise a query of just"%"would return every row).Input is validated with Zod before it reaches any business logic — length limits, enum constraints on
priority/status— rejecting malformed input with a clear error instead of passing it through.Stored ticket text is framed as data, not instructions.
subject/descriptionare free-text, and a ticket created now is read back verbatim by a laterlist_customer_ticketscall — a second-order prompt-injection vector. Response text explicitly notes that this content is stored customer input, not directives. This is a mitigation, not a guarantee.No authentication or authorization. This is a local, single-user demo — anyone who can spawn the process has full access to every tool, including full customer PII. Explicitly out of scope here; would have to change before this pattern touched real, multi-tenant data.
No secrets anywhere in the project. No API keys, tokens, or credentials; the only external dependency is the local SQLite file, which is gitignored.
Example Claude interactions
Read-path prompts, once connected:
"Search for a customer named Chen."
"Get full details for customer cust_004."
"What open tickets does cust_005 have?"
The interesting one is the write path:
You: "Create a high-priority support ticket for cust_002 about their tracking numbers not syncing — but check with me before you actually create it."
Expected behavior: the model calls
search_customers/get_customeras needed, then either asks you to confirm before callingcreate_support_ticket, or calls it once withapprovedfalse/omitted, gets rejected, and surfaces the proposed ticket back to you. Either way, nothing is written until you've actually agreed and the model calls it again withapproved: true.
More scripted walkthroughs, including forcing the rejection path directly to see the raw
enforcement message: docs/demo-script.md.
Local setup
Requires 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.jsThe server communicates over stdio — no HTTP port, nothing to browse to directly.
Connecting to Claude Code: this repo includes a project-scoped .mcp.json (generated via
claude mcp add opsbridge --scope project -- node dist/index.js, so it's exactly what the CLI
itself produces, not hand-written). Build first, then approve it once:
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 - ✔ ConnectedConnecting any other MCP client (Claude Desktop, etc.) — most read a JSON config with a
command/args pair:
{
"mcpServers": {
"opsbridge": {
"command": "node",
"args": ["/absolute/path/to/opsbridge-mcp/dist/index.js"]
}
}
}Poking at it manually without a full client — the MCP Inspector, version pinned deliberately
(an unversioned npx @modelcontextprotocol/inspector can resolve to a stale cached build instead
of the current release):
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 # headlessTesting
npm test # vitest — 33 tests across 6 files
npm run typecheck
npm run lintTests connect a real MCP Client to a real McpServer over the SDK's InMemoryTransport, backed
by a fresh in-memory SQLite database per test (tests/helpers.ts) — exercising the actual
request → Zod validation → tool handler → response path a real client goes through, not just the
domain functions in isolation. Coverage includes: successful and empty-result search, customer not
found, ticket listing with/without a status filter, invalid input across every tool, ticket
creation rejected both with approved: false and with approved omitted entirely, successful
creation, duplicate-submission safety, LIKE-wildcard escaping, prompt-injection framing text, and
audit-log content (including that PII never appears in a log line) for every tool.
Limitations
Deliberate scope cuts for a focused demo, not oversights:
No authentication, authorization, or per-user data scoping — see Security considerations.
The approval flag isn't a verified human signal — it's a boolean a model could set on its own initiative; see Approval mechanism.
No pagination — search is capped at 10 results; ticket lists are unbounded but the dataset is tiny.
No update or delete tools — only ticket creation is a write action.
stdio transport only — no HTTP/SSE, no remote deployment story.
No rate limiting or idempotency key on
create_support_ticket— a retried call creates a second, independent ticket rather than being deduplicated.SQLite, single process — no connection pooling, no migration tooling beyond
CREATE TABLE IF NOT EXISTS.Audit log is a local stderr stream — not shipped anywhere, not queryable, no retention policy.
What I'd change for production
If this pattern were ever pointed at real customers instead of synthetic demo data:
Move off stdio to Streamable HTTP with OAuth bearer auth, scoped per tenant/customer — the SDK already supports this transport; today's stdio model implicitly trusts whoever can spawn the process, which is fine for a local demo and nowhere else.
Add real authorization mapping the authenticated caller to which customers/tickets they may touch — every tool is currently unscoped.
Make approval verifiable, not just present — use MCP elicitation to force a real round-trip confirmation back to a human, or require a short-lived token minted by a separate confirmation step outside the model's control.
Swap SQLite for Postgres with pooled connections and a real migration tool.
Ship the audit log somewhere durable and queryable (not stderr) with retention and access controls appropriate for what it's auditing.
Add rate limiting and an idempotency key on the write path.
Add pagination to
search_customersandlist_customer_tickets.Add observability — latency, error rate, and call volume per tool.
Run typecheck/test/lint in CI on every change, not just locally on demand.
None of this is implemented here — the point of this project is to demonstrate the pattern correctly at small scale, not to pre-build infrastructure a real deployment would need but a demo doesn't.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to perform CRUD operations on a SQLite database, with all tools protected by Google OAuth 2.0 authentication.
- AlicenseNot gradedqualityCmaintenanceEnables AI models to execute SQL queries against a SQLite database and receive results as JSON.72MIT
- 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
Related MCP Connectors
Runtime permission, approval, and audit layer for AI agent tool execution.
Deterministic compliance and vertical knowledge bases for autonomous agents. Free 24hr trial.
Pre-action allow/deny for AI agents. 24 statutes, 13 jurisdictions: EU AI Act, GDPR, DPDP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ivantagesam/opsbridge-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server