Skip to main content
Glama
DykstraSound

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