mcp-server-reference
# mcp-server-reference
**Ungoverned MCP is an uncatalogued API, one layer up.**
A small [Model Context Protocol](https://modelcontextprotocol.io) server that publishes a static OpenAPI catalog, a written contract, and an explicit deny-list. It is a reference surface — not a platform and not a SaaS.
Bruno Carvalho de Aquino, Solutions Architect.
```text
MCP host (Cursor, Claude, …)
|
| stdio
v
+---------------------------+
| mcp-server-reference |
| |
| tools = catalog lookup |
| resources = contract |
| deny-list = interface |
+---------------------------+
|
| imported modules only
v
orders | identity | billing
(OpenAPI snippets, no fetch)
```
## Problem
Teams are wiring MCP servers the way they once wired shadow APIs: a process, a few powerful tools, no inventory, no owner, no statement of what the process must not do.
A tool that accepts a URL is an HTTP proxy. A tool that accepts a shell string is remote code execution. A tool that dumps `process.env` is credential exfiltration. The protocol does not make those shapes safe. Naming them "tools" just hides them from the people who review APIs.
## What this repository is
| Piece | Role |
|---|---|
| TypeScript MCP server (`@modelcontextprotocol/server` v2) | stdio process a host can spawn |
| Five tools | list / lookup / describe. No execution. |
| Four resources | contract, deny-list, catalog index, per-API spec |
| [CONTRACT.md](CONTRACT.md) | Human manifest of the surface |
| [DENY.md](DENY.md) | Closed list of capabilities that will not be added |
| [ADR-0001](docs/adr/0001-governed-mcp-surface.md) | Why the deny-list is part of the interface |
The catalog is three lab OpenAPI documents compiled into the process (`orders`, `identity`, `billing`). Spec `servers[]` use the `.invalid` TLD. The process never fetches them.
## What this repository is not
- **Not Apidex.** Apidex is the product-shaped answer (inventory, lifecycle, MCP governance). This repo is the public wedge: one honest server and a written no.
- **Not an MCP security gateway.** No OAuth, no per-principal allowlist, no audit shipping. That is a later box.
- **Not a customer case study.** No invented counts of servers, tokens, or "shadow MCP reduced by N%".
## Constraints
- Node 20+. TypeScript strict. No runtime filesystem walk. No `fetch`. No `child_process`.
- Tool inputs are closed: slugs, HTTP methods, exact path templates, deny-list enums.
- `billing` is **deprecated** on purpose. A catalog that hides sunset APIs is how integrations rot.
- Identity's published slice has no password or token endpoints. Restricted classification is metadata, not theatre.
## Threat notes
| Risk | How it shows up in MCP | Control here |
|---|---|---|
| SSRF | `fetch(userUrl)` or "load this swagger" | No URL arguments. Specs are imported. |
| RCE | `run_command`, `bash -c` | Not implemented. `explain_deny("raw_shell")`. |
| Secret exfil | `debug_env`, home-directory `read_file` | No env or path tools. No secrets in the repo. |
| Prompt injection via specs | Remote OpenAPI that contains instructions | Catalog is reviewed git content. |
| Confused deputy | Host user grants the server; model picks the tool | Small surface. Contract is listable. |
| Shadow execution | Lookup tool that quietly calls the live API | `get_operation` returns `invoked: false`. |
This is a threat *note*, not a full STRIDE model of a production estate. If you attach this server to a host, you still trust the host process and the model. Reducing that trust is gateway work.
## How to run locally
```bash
npm install
npm test
npm run typecheck
npm run inspect # prints the contract JSON
npm run build
```
stdio server (what a host spawns):
```bash
npm start
# or, without a prior build:
npm run dev
```
Do not type into that process. MCP is JSON-RPC on stdin/stdout. Talk to it from a host.
### Cursor / Claude Desktop
```json
{
"mcpServers": {
"catalog": {
"command": "node",
"args": ["/absolute/path/to/mcp-server-reference/dist/index.js"]
}
}
}
```
Build first. The host must be able to spawn Node. After connect, call `inspect_contract` or read `mcp://contract`.
### Smoke without a host
```bash
npm run inspect
```
You should see the five tools, the four resources, and the seven deny-list ids.
## Relation to API governance
OpenAPI governance already has a vocabulary: owner, lifecycle, breaking change, deprecation, "this operation is not public". MCP arrived and forgot that vocabulary.
| API practice | MCP equivalent in this repo |
|---|---|
| Published OpenAPI | `CONTRACT.md` + `inspect_contract` |
| Path allowlist | Catalog ids + exact operation match |
| "Do not expose /admin" | [DENY.md](DENY.md) |
| Deprecated API still listed | `billing` lifecycle = deprecated |
| Contract test in CI | `npm test` + `npm run typecheck` |
If you would fail a PR that adds an undocumented REST endpoint, fail the PR that adds an undocumented MCP tool.
The product-shaped version of this problem — inventory of APIs **and** MCP servers, lifecycle, review — is Apidex. This repository does not implement that. It shows the primitive a catalog would ingest: a server that can describe itself and state what it refuses.
## Trade-off I accepted
A kitchen-sink demo is more impressive in a screenshot. It also trains the next engineer to add `fetch` "just for this". I chose a boring catalog so the deny-list is the point.
The cost: you cannot use this server to "do work". That is intentional. Execution is a different contract, with identity.
## License
[MIT](LICENSE). Copyright (c) 2026 Bruno Carvalho de Aquino.
TDQS
Scored across 5 tools
Each tool has a completely distinct purpose: listing catalogs, fetching a specific API document, retrieving a single operation, inspecting the server contract, and explaining deny behavior. No overlap or ambiguity exists.
All tool names follow a consistent lowercase verb_noun pattern (list_catalog, lookup_api, get_operation, inspect_contract, explain_deny), making the surface predictable and easy to navigate.
Five tools is precisely scoped for a read-only reference server. Each tool fills a necessary role without redundancy, and the count is neither too sparse nor overloaded for the stated purpose.
The tool set covers the full lifecycle for exploring static OpenAPI catalogs: listing, retrieving documents, drilling into operations, inspecting the server contract, and understanding denied capabilities. No apparent dead ends or missing operations for its domain.