McProxy
by DykstraSound
README.md
# McProxy
A local, multi-tenant [Model Context Protocol](https://modelcontextprotocol.io) server framework for
macOS, paired with a browser-based connection manager at `127.0.0.1:7337`. It puts several
third-party services behind one MCP server, so a client like Claude Desktop or Claude Code sees one
process instead of one per integration.
## What it deliberately does not include
McProxy ships **zero connectors**. `src/connectors/descriptors/` is empty and the descriptor
registry is an empty array — there is nothing to configure, nothing to authorize, and no tool beyond
`ping` until you add one. This is the framework's central decision, not an oversight: shipping
someone else's integration would ship the shape of their credentials too. Every connector — its
fields, its auth flow, its probe — is added per install, either by a human following a guide or by an
AI agent running a setup interview.
## Quick start
```bash
git clone https://github.com/DykstraSound/McProxy.git
cd McProxy
npm install
cp .env.example .env
npm run gui
```
`npm run gui` opens the connection manager in your browser. With no connectors configured yet, the
page has nothing to show — that's expected. The MCP server itself (`npm run dev`, or `dist/server.js`
after `npm run build`) also runs fine at this point; it answers `ping` and nothing else until a
connector exists.
## Adding your first connection
See [`docs/ADDING-A-CONNECTOR.md`](docs/ADDING-A-CONNECTOR.md) to write a connector descriptor
yourself, or [`docs/AGENT-SETUP.md`](docs/AGENT-SETUP.md) to have an AI agent walk you through it.
## The descriptor model
Every connector is one file exporting one object: fields, auth kind, a live probe, an optional
identity check, and an optional reconnect flow. The GUI, the `.env` resolver, and the tool-layer
account scoping are all generic over this one shape — adding a connector never touches any of them.
```ts
export default defineConnector({
id: "example",
displayName: "Example",
kind: "native",
envPrefix: "EXAMPLE", // {PREFIX}_ACCOUNTS / {PREFIX}_{SLUG}_{FIELD}
multiInstance: true,
manageable: true,
auth: "oauth",
fields: [{ key: "API_KEY", label: "API key", secret: true, required: true }],
async probe(inst) { /* live reachability check — never rejects */ },
async identity(inst) { /* real remote account, for the identity-mismatch check */ },
authorize(inst) { return { script: "scripts/authorize-example.ts", args: [inst.slug] }; },
});
```
## What it guarantees
- **Third-party content is fenced before it reaches a model.** Every tool result that carries data
fetched from a connected service is wrapped in a nonce-tagged envelope, minted after the payload
exists so nothing in it can forge a matching close marker (`src/shared/tools/untrusted.ts`).
- **Secrets never leave the server through the API.** The connection list masks every field marked
`secret` to a four-character prefix/suffix and the write-side form never echoes a stored value back
(`src/gui/api.ts`, `maskSecret` / `fieldViews`).
- **`.env` is written by exactly one module**, which preserves comments and untouched lines byte for
byte and takes a timestamped backup before every write (`src/connectors/env-file.ts`).
- **Every file under `~/.mcproxy` is 0600 and every directory is 0700**, enforced on every server and
GUI start, not just at creation (`src/shared/secure-fs.ts`).
- **A probe never answers from a cache.** Instance resolution is never memoized — a `.env` edit is
live on the next call, not the next restart (`src/connectors/instances.ts`) — and the cached status
a connection displays is written only by an explicit Test or reconnect, never inferred
(`src/connectors/status.ts`).
- **Absence is reported as absence, never as health.** A connector nobody has tested renders
`never-checked`, not a default "ok" (`src/connectors/status.ts`); a config change is detected by
re-resolving live state, never assumed from what was last observed.
## Requirements
macOS, Node ≥ 22. Platform-agnostic support is an explicit non-goal.
## License
MIT
TDQS
A4.1/5.0
Scored across 1 tool
Disambiguation5/5
With only a single tool, there is no possibility of confusion between tools. The ping tool's purpose is clearly stated as a health check.
Naming Consistency5/5
The single tool name 'ping' is a clear, conventional verb that matches its read-only health-check function. With one tool there is no pattern inconsistency to penalize.
Tool Count2/5
A server named McProxy with only one health-check tool feels far too thin for its apparent scope. A proxy server would typically expose multiple operational tools, making one tool insufficient.
Completeness1/5
The tool surface provides only liveness verification and no actual proxy functionality, which is severely incomplete given the server name. Agents expecting proxy capabilities would hit a dead end immediately.
Maintenance
ActivitySlowing
ResponsivenessNo issues