Skip to main content
Glama
README.md
# 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

A4.1/5.0

Scored across 5 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness5/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues