mcp-onprem-starter
by Ricoledan
README.md
# mcp-onprem-starter
A starter template for building single-client MCP servers that expose an existing REST API through MCP. The primary
deployment profile runs as a stdio subprocess in a customer-controlled environment, including private cloud,
data-center, and air-gapped infrastructure.
## Architecture
```text
MCP client / agent
│
│ stdio
▼
MCP server
├── tool handlers
├── write authorization
├── typed errors
└── HTTP client
│
▼
Configured upstream API
```
> **Development note:** When `UPSTREAM_BASE_URL` is unset, the server uses
> the bundled mock upstream so the request path can run locally without an
> external API.
## Quick start
```sh
npm install
npm run dev
```
For a reproducible development environment, use the optional Nix flake:
```sh
nix develop
npm ci
just verify
```
The development shell provides Node.js, `just`, and the Docker CLI. Nix is a developer-environment option; the server's
runtime and package installation remain based on Node.js and npm.
This starts the server over stdio with a bundled mock upstream and the development defaults. Point an MCP client (Claude
Code, an agent runtime, or the SDK's own test client) at it and call `list_records` or
`create_record`.
> **Template note:** `list_records`, `create_record`, and the bundled mock
> are illustrative examples. Replace them with tools and upstream mappings
> derived from the operator's business request.
To verify the whole deployable chain — build, container boot, a real protocol round trip, and a rollback drill — run:
```sh
just verify
```
Docker is the default container runtime. Podman is also supported:
```sh
CONTAINER=podman just verify
```
## What's included
| Path | What |
|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `src/index.ts` | Entry point. `buildServer(deps)` is exported separately from `main()` so tests can construct a server independently of stdio and environment loading. |
| `src/config.ts` | Zod-validated config. Structural settings are validated at startup with the variable named; capability settings (secrets) isolate the tool that requires them. |
| `src/errors.ts` | A closed error-code taxonomy, one error class, and the four rules that keep it from drifting (see the file's header comment). |
| `src/logger.ts` | stderr-only structured logging with automatic secret redaction and a separate audit stream for writes. |
| `src/http/client.ts` | REST client with host-named timeout errors, a single guarded re-auth retry, normalized failure surfaces, retryable reads, and explicit write-outcome handling. |
| `src/tools/` | The tool-module shape (`name`, `title`, `description`, `annotations`, `inputSchema`, `handler`), plus one read and one write example. |
| `src/mock/server.ts` | A tiny in-memory upstream so the template runs with zero configuration. |
| `DEPLOYMENT.md` | The three current distribution paths, configuration reference, operational notes, and coverage roadmap. |
| `docs/scaling.md` | The separate shared HTTP deployment profile and its requirements. |
| `docs/roadmap.md` | Everything else considered and deferred, with the trigger for building each. |
## Implementing an operator request
Agents implementing a business request should first identify the required workflows, upstream API contract, tool
behavior, security requirements, and acceptance criteria. See `AGENTS.md` for the complete implementation workflow and
repository rules.
## Adapting this template
Use this sequence to build the business-specific server:
1. **Understand the structure.** Read `src/index.ts`, `src/config.ts`,
`src/http/client.ts`, `src/tools/shared.ts`, and `src/errors.ts`.
2. **Define the upstream contract.** Identify the API base URL, authentication method, endpoints, request and response
schemas, and error behavior.
3. **Design the MCP tools.** Map business operations to tools with clear names, descriptions, input schemas,
annotations, and result shapes.
4. **Implement the tool handlers.** Add one module per tool under
`src/tools/`, use the shared HTTP client, apply `checkWriteGate()` to writes, and register each tool in
`src/tools/index.ts`.
5. **Add configuration.** Extend `ConfigSchema` for required URLs, credentials, timeouts, or capability settings. Keep
environment access inside `src/config.ts`.
6. **Add tests.** Cover tool inputs, upstream responses, error mapping, write authorization, and the MCP
initialize/tool-listing round trip.
7. **Verify the deployment.** Run `npm run ci`, then `just verify` for the container, non-root boot, stdio, and rollback
checks.
### Template cleanup
After the business tools are implemented:
- Replace `src/tools/_example-read.ts` and `src/tools/_example-write.ts`.
- Update the bundled mock and its tests to represent the business domain, or retain them as a local contract-test
harness.
- Remove placeholder endpoint definitions and example terminology.
- Update `.env.example`, `README.md`, and `DEPLOYMENT.md`.
- Keep the shared configuration, HTTP client, error taxonomy, write gate, logging, and verification structure when they
still match the deployment.
## Design principles
- **stdio by default.** The server exposes the MCP connection through stdio. The agent runtime spawns this as a child
process inside its own trust boundary.
- **Validate at startup, isolate optional capabilities.** A malformed required setting stops startup with the variable
name in the message. A missing optional credential disables just the tool that needs it — the server still starts, and
the disabled tool stays listed rather than silently vanishing.
- **Writes require deliberate authorization.** A write tool needs both an environment- level flag and a per-call
confirmation, and the underlying HTTP client surfaces an ambiguous write outcome for operator review rather than
retrying automatically.
- **Every error is actionable.** Error details are sanitized before they reach the caller, and every error names what to
do next.
- **Patterns have production provenance.** Every pattern in this template is ported from a pattern already shipped in a
working server — see the doc comments for provenance.
## License
MIT.
TDQS
A4.4/5.0
Scored across 2 tools
Disambiguation5/5
The two tools have clearly distinct purposes: list_records for querying/searching with pagination, and create_record for inserting new records. No overlap or ambiguity exists.
Naming Consistency5/5
Both tools follow a consistent verb_noun pattern (list_records, create_record), making the API predictable and easy to navigate.
Tool Count3/5
At 2 tools, the set is on the thin side, but for a 'starter' server it could be intentionally minimal. However, the reference to get_record suggests at least one more expected tool.
Completeness2/5
The set is missing get_record, update_record, and delete_record. list_records explicitly instructs agents to use get_record, but that tool does not exist, creating a clear dead end and significant coverage gap.
Maintenance
ActivityStale
ResponsivenessNo issues