wmcp
by mwhobrey
README.md
# wmcp
**MCP exposure infrastructure** — how to run Model Context Protocol servers so Cursor, Claude Desktop, and Claude Code can reach them from WSL, Windows, or macOS.
This repo is the transport/wiring layer, plus an optional **personas** server (`compose` / `list_prompts`). It also ships a tiny `example` server (`echo` / `ping`) so you can verify the plumbing. Bring your own tools and prompt packs.
**Requires Node.js ≥ 18.**
## What an MCP server is (short version)
An MCP server is a process that speaks [Model Context Protocol](https://modelcontextprotocol.io) — JSON-RPC messages describing **tools**, **prompts**, and/or **resources**. Clients (Cursor, Claude) call those tools during a chat.
The interesting part is not the tools themselves. It is **how the client connects**:
| Transport | Who speaks it | How it runs |
|-----------|---------------|-------------|
| **stdio** | Cursor (local), Claude Desktop | Client spawns `node dist/….js` and talks over stdin/stdout |
| **Streamable HTTP** | Cursor, Claude Desktop (URL mode) | One long-lived Node HTTP server; client POSTs to `http://host:port/<name>` |
| **Legacy SSE** | Claude Code CLI | Same HTTP server; `GET /<name>/sse` + `POST /<name>/message` |
`wmcp` implements all three from one codebase.
```
┌─────────────────┐ stdio ┌──────────────────┐
│ Cursor / Claude │ ◄────────────► │ example-server │ (per-process)
│ (local spawn) │ │ or stdio-bridge │
└─────────────────┘ └──────────────────┘
┌─────────────────┐ HTTP/SSE ┌──────────────────┐
│ Cursor / Claude │ ◄────────────► │ http-server │──► example
│ (URL mode) │ │ :8820 │──► personas (opt)
└─────────────────┘ └──────────────────┘
```
Typical setup when the real Node runtime lives in **WSL**:
1. Run `http-server` inside WSL (systemd user unit or `npm start`).
2. Point Windows/mac Cursor at `http://localhost:8820/example` (WSL localhost forwarding).
3. Or, for stdio-only clients on Windows: `wsl.exe … node dist/example-server.js`.
On **macOS / native Linux**, skip the `wsl.exe` path — run Node locally and point Cursor at `http://localhost:8820/<server>` or a direct `node dist/….js` stdio command.
## Quick start
```bash
git clone https://github.com/mwhobrey/wmcp.git
cd wmcp
npm install
npm run build
npm start # HTTP front door on :8820
curl http://localhost:8820/health
```
stdio smoke test (separate terminal):
```bash
node dist/example-server.js
# or: node dist/stdio-bridge.js example
```
### Wire Cursor
If `transport` is `"sse"` (the default in the example config), **start the HTTP server first** (`npm start`) and leave it running. Cursor connects to URLs; nothing is listening otherwise.
**Option A — paste a minimal entry** (safest; does not touch other MCP servers):
```json
{
"mcpServers": {
"example": {
"url": "http://localhost:8820/example"
}
}
}
```
Add that under your existing `mcpServers` in Cursor's MCP config, then reload MCP and call `ping` on `example`.
For stdio instead of HTTP:
```json
{
"mcpServers": {
"example": {
"command": "node",
"args": ["/absolute/path/to/wmcp/dist/example-server.js"]
}
}
}
```
**Option B — generate configs from this repo** (WSL + Windows):
> Warning: `npm run sync:mcp` **overwrites** `~/.cursor/mcp.json` and, when `/mnt/c` is present, `C:\Users\<windowsUser>\.cursor\mcp.json`. Back up first if you already have other servers configured.
1. Copy and edit config:
```bash
cp config/mcp-servers.example.json config/mcp-servers.json
# set projectRoot, nodePath, wslUser, windowsUser
```
2. With the HTTP server running (for `"transport": "sse"`):
```bash
npm run sync:mcp
```
3. Reload MCP in Cursor. Call the `ping` tool on the `example` server.
`transport` in `mcp-servers.json`:
- `"sse"` (default) → Cursor entries are `{ "url": "http://localhost:8820/example" }` — **http-server must be up**
- `"stdio"` → Cursor spawns Node (or `wsl.exe` on Windows) — no shared HTTP process needed
## Optional: personas server
Markdown prompt packs with hot reload. Tools:
| Tool | Purpose |
|------|---------|
| `compose` | Merge layers into one system prompt (see order below) |
| `list_prompts` | List loaded prompts by category |
Also exposes MCP **prompts** (`list` / `get`) for clients that use that capability.
**Compose merge order** (each layer optional except when `base` is left on):
1. `base` (`personas/base`) — omitted if `base: false`
2. `team` — repo/org policy card
3. `persona` — role
4. `skill` — mode
5. `script` — playbook
Sections are joined with `---`. Template args apply to every included layer.
**Enable on the HTTP front door:**
```bash
cp env.example .env # then set WMCP_ENABLE_PERSONAS=1
# PERSONAS_DIR=/absolute/path/to/prompts # optional; default ./prompts
npm start
# → http://localhost:8820/personas
```
Cursor URL entry:
```json
{
"mcpServers": {
"personas": {
"url": "http://localhost:8820/personas"
}
}
}
```
**Or run stdio directly** (no `WMCP_ENABLE_PERSONAS` required):
```bash
npm run start:personas
# or: node dist/stdio-bridge.js personas
```
Sample prompts ship under `prompts/` (`personas/`, `skills/`, `scripts/`, `teams/`). Replace them with your own — edits hot-reload; no restart needed.
Frontmatter shape:
```markdown
---
name: engineer
description: Short description for list_prompts
category: persona
args:
- name: stack
description: Primary tech stack
required: false
---
# Role: Engineer
{{stack}}Primary stack: {{stack}}.{{/stack}}
```
Template syntax: `{{var}}`, `{{var|default}}`, and `{{var}}…{{/var}}` (block omitted when unset).
## Adding your own MCP server
1. Create `src/my-server.ts` — construct an MCP `Server`, register tools, expose `getServer()`.
2. Register a factory in `src/registry.ts`.
3. Add a `servers.my` entry in `config/mcp-servers.json` with `"script": "my-server.js"`.
4. `npm run build && npm run sync:mcp` (and restart `http-server` if using URL mode).
The HTTP and stdio layers stay unchanged. They only need a name → `() => Server` factory.
## Layout
```
src/
example-server.ts # reference MCP tools (echo, ping)
personas-server.ts # optional compose / list_prompts
registry.ts # name → factory map (+ env toggles)
http-server.ts # Streamable HTTP + legacy SSE multiplexer
stdio-bridge.ts # named stdio launcher
env-flags.ts
is-main-module.ts
prompts/ # sample prompt pack (swap for yours)
config/
mcp-servers.example.json
scripts/
sync-mcp-config.mjs
fix-permissions.mjs
systemd/
wmcp.service
```
## Environment
| Variable | Default | Purpose |
|----------|---------|---------|
| `MCP_PORT` | `8820` | HTTP listen port |
| `MCP_SSE_PING_MS` | `30000` | SSE keep-alive comment interval (`0` disables) |
| `WMCP_ENABLE_PERSONAS` | off | Register `personas` on the HTTP front door (`1`/`true`/`yes`/`on`) |
| `PERSONAS_DIR` | `./prompts` | Markdown prompt root (`personas/`, `skills/`, `scripts/`, `teams/`) |
See `env.example`. Load via `.env` in the project root (`dotenv`) or export in the shell / systemd unit.
## systemd (optional)
```bash
# edit User / paths in systemd/wmcp.service first
mkdir -p ~/.config/systemd/user
cp systemd/wmcp.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now wmcp
systemctl --user status wmcp
```
To enable personas under systemd, set `WMCP_ENABLE_PERSONAS=1` in `.env` (or `Environment=` in the unit).
## Design notes
- **One HTTP process, many logical servers** — routes by path prefix; each request gets a fresh MCP `Server` instance for Streamable HTTP (stateless).
- **Fail per server at boot** — a broken factory is skipped; others still serve.
- **Personas are optional** — off by default so the public repo stays a transport template; flip the env when you want compose/list_prompts on HTTP. Direct `npm run start:personas` always works.
- **Private prompt packs stay private** — point `PERSONAS_DIR` at an external directory, or replace `prompts/` locally and don't commit company content.
## License
MIT
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues