Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mcpx",
"version": "0.18.0",
"version": "0.18.1",
"license": "MIT",
"bin": {
"mcpx": "./src/main.ts"
Expand Down
203 changes: 19 additions & 184 deletions src/index.md
Original file line number Diff line number Diff line change
@@ -1,202 +1,37 @@
# mcpx

`mcpx` turns registered MCP servers into an agent-friendly command surface.
Use it when a task needs MCP-backed services such as PostHog, Sentry, Cloudflare,
or another server registered in the user's global mcpx registry.
Acts as the user's global MCP registry (`~/.agents/mcpx/servers.json`): every registered server (PostHog, Sentry, Cloudflare, ...) is mounted as a command group `<server>`, and each MCP tool is `<server>.<tool>`. Preflight: bare `mcpx` lists the registered servers.

## Execution Rules
The surface depends on the registry, so read only the slice you need: `mcpx @schema .<server>`, a tool with `mcpx @schema .<server>.<tool>`, a shortlist with `mcpx @schema '.<server>.{a,b,c}'`. Bare `mcpx @schema` dumps every server and burns context; use it only when the user asks for the whole surface. Deeper material: `mcpx @skill references/<file>`.

- Run `mcpx` directly.
- Start with the server inventory instead of dumping every registered schema.
- Call MCP tools as `mcpx <server>.<tool> "<object>"`.
- Pass one quoted object token, or the whole object as YAML/JSON5 via `@file` / stdin (`-`), as the complete tool input.
- Use focused schema selectors before calling tools on large MCP surfaces.
- Do not hand-edit `~/.agents/mcpx/servers.json` or token cache files unless the user explicitly asks for registry surgery.
- Treat MCP server registration and OAuth setup as human-owned configuration. If a required server is missing or unauthenticated, stop and ask the user to configure it.
## Everywhere

## First Move: Discover The Surface
- Registration and OAuth are human-owned. If a needed server is missing or unauthenticated, stop and ask the user. Don't hand-edit `servers.json` or token caches unless they ask for registry surgery.
- Default output is agent-optimized (YAML for structured content, text passed through, media saved as `file saved <path>`). Pass `--context "{ output: 'raw' }"` only when exact server output matters.

At the start of an MCP task, run bare `mcpx` to list configured servers:
## Approaching a server — `.<server>`

```bash
mcpx
```
1. Pick the likely server from the request and run `mcpx @schema .<server>`.
2. Follow the schema's status line: if it says fully shown, call the tool; if only an outline, narrow to the candidate tools with `.<server>.{a,b}`.

Choose the likely server from the user's request, then inspect only that server's
schema:
## Calling a tool — `.<server>.<tool>`

```bash
mcpx @schema .sentry
mcpx @schema .posthog
```
- Call as `mcpx <server>.<tool> "<object>"`. The object is the complete tool input: one quoted token, or the whole object as YAML/JSON5 from `@file` or stdin (`-`).
- Put multi-line text in a quoted heredoc with `|` block scalars. Field values are literal: never pass `-` or `@path` as a field value, and never mix the object with `--flag`s. Quote scalars YAML would retype (`'1.10'`, `'012'`, `'true'`). A file or stdin starting with `{` is read as JSON5.
- Tool results may include `$notifications` (progress, server events). Ignore them unless the task depends on them; format and special cases: `mcpx @skill references/notifications.md`.

After that, narrow to the specific tool or a short candidate list:
## Registering servers — `@add`, `@login`, `@remove`

```bash
mcpx @schema .posthog.projects-get
mcpx @schema '.posthog.{projects-get,alerts-list,alert-create}'
```
Only when the user asks. `@add` takes an HTTP `url` (default) or `transport: 'stdio'` with `command`/`args`; after that both behave identically. Bearer auth goes in `bearer`, and `env:NAME` keeps the secret out of the registry (an array round-robins credentials). `@login "{ name: '<server>' }"` needs a TTY; otherwise ask the user to run it. Input shapes: `mcpx @add --help`.

Avoid bare `mcpx @schema` unless the user explicitly asks for the complete command
surface. On a real registry it may expand every server schema and waste the
agent context.
## Project-local skills — `@skill-install`

## Call Tools
When the user wants a project to expose only an approved set of servers, `mcpx @skill-install "{ servers: 'posthog,sentry' }"` writes `.agents/skills/mcpx/` (router plus install and server-recovery references). `{ show: 'sentry' }` prints a one-server skill to stdout without writing files. It does not replace `@add`.

Use inline JSON/JSON5 for tiny inputs:
## Daemon — `@daemon`

```bash
mcpx posthog.projects-get '{}'
mcpx sentry.search-issues '{"query":"is:unresolved"}'
```

For non-trivial payloads, put the whole input object in a quoted heredoc (or an
`@file`) as YAML — nothing to escape, multi-line text goes in `|` block scalars:

```bash
mcpx <server>.<tool> - <<'EOF'
query: is:unresolved
note: |
multi-line text
stays verbatim
EOF

mcpx <server>.<tool> @payload.yaml
```

Field values are always literal: never pass `-` or `@path` as a field value, and
never mix the object with `--flag`s. Quote scalars YAML would type (`'1.10'`,
`'012'`, `'true'`). A file or stdin starting with `{` is read as JSON5 instead.

Default output is optimized for agents (YAML for structured content,
direct text passthrough, media saved as `file saved <path>`). Pass
`--context "{ output: 'raw' }"` when exact server output is required. Raw output
may include a JSON notification envelope — see Notifications below.

## Server Transports

mcpx supports two MCP transports, both configured through `mcpx @add`:

- **HTTP** — default. Remote MCP services like PostHog or Sentry.
- **Stdio** — local processes started by mcpx, for example
`mcpx @add "{ name: 'fs', transport: 'stdio', command: 'bunx', args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp/fs-sandbox'] }"`.

HTTP bearer auth accepts `bearer` in the input object. Prefer `env:NAME` so
secrets stay out of the registry. An array configures round-robin credentials:

```bash
mcpx @add "{ name: 'posthog', url: 'https://mcp.posthog.com/mcp', bearer: ['env:POSTHOG_TOKEN_A', 'env:POSTHOG_TOKEN_B'] }"
```

After registration both behave identically from the agent's view: same schema
discovery, same `mcpx <server>.<tool> '<object>'` call pattern, same output
shape. You do not need to differentiate when calling tools.

## Notifications

Most tools emit no notifications and this section never applies.

When an MCP server emits events during a call (progress reporting, server-side
state changes, custom events), mcpx merges them into default structured output
under an injected `$notifications` field:

```yaml
count: 1
$notifications:
- method: notifications/progress
params:
progressToken: '...'
progress: 3
total: 4
message: step 3
```

For non-JSON text, binary, or mixed content, mcpx falls back to the trailing
sentinel line:

```
<tool result lines>
$notification: [{"method":"notifications/progress","params":{...}}]
```

Notifications are objects with these fields:

- `method` — MCP notification method name
- `params` — method-specific payload (shape depends on `method`)
- `aggregatedCount` — only on the **last** `notifications/progress` entry per
progress token; indicates that N intermediate progress entries were collapsed
to keep output bounded. The first and last entries are preserved verbatim.

Notification methods you may encounter:

| method | meaning |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `notifications/progress` | Progress for a long-running tool. Carries `{ progressToken, progress, total?, message? }`. |
| `notifications/tools/list_changed` | Server's tool schema changed. mcpx automatically refreshes the registry; you do not need to react. |
| `$oversize` | Synthetic raw-mode marker. Means notifications were saved to a temp JSON file. Default output renders this as `notifications oversize, saved to <path>`. |
| other (e.g. `notifications/custom/*`) | Passed through verbatim. Server-specific. |

In raw context with a structured result and non-empty notifications, the
trailing sentinel line is replaced by a JSON envelope on stdout:

```json
{ "result": <tool-result>, "notifications": [ ... ] }
```

Text results keep the trailing `$notification:` line even in raw context,
because text content is not JSON and wrapping it would break consumers.

Practical guidance:

- If your task does not depend on progress or server events, ignore notifications.
- Check `$notifications`, the sentinel line, or the raw envelope only when present.
- Do not assume notifications appear; the common case is no `$notification:` at all.

## Project-Local Skills

When the user has already configured the relevant global MCP servers and wants a
project to expose only that approved set to agents, generate a project-local mcpx
skill:

```bash
mcpx @skill-install "{ servers: 'posthog,sentry' }"
```

This writes:

```text
.agents/skills/mcpx/SKILL.md
.agents/skills/mcpx/references/install.md
.agents/skills/mcpx/references/servers.md
```

Use the generated project skill as the project-specific router. It names the
approved servers and includes troubleshooting references so a collaborator's
agent can install `mcpx` and reconstruct those servers if their machine is
uninitialized. Do not use `@skill-install` itself as a substitute for `@add`.

For temporary agent guidance without writing `.agents/skills`, print a
single-server skill to stdout:

```bash
mcpx @skill-install "{ show: 'sentry' }"
```

Use this when the user asks for a one-off service action during the current
agent session and does not want persistent project skills.

## References

mcpx maintains a background daemon (`mcpxd`) for session reuse and notification
buffering. You normally do not need to interact with it. If a tool call behaves
unexpectedly (stuck session, surprising eviction, schema drift), see
[references/daemon.md](references/daemon.md) before filing an issue.
`mcpxd` keeps sessions warm and is normally invisible. For stuck sessions, surprising eviction or schema drift read `mcpx @skill references/daemon.md` before filing an issue.

## Feedback

If mcpx behaves unexpectedly, capture the exact command, schema selector, input,
and structured error, then file an issue:

```bash
gh issue create -R celados/mcpx --title "bug: <summary>" --body "<repro steps and error output>"
```
Capture the exact command, selector, input and structured error, then `gh issue create -R celados/mcpx --title "bug: <summary>" --body "<repro>"`.
59 changes: 59 additions & 0 deletions src/references/notifications.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# mcpx notifications

Selector for any server: `mcpx @schema .<server>`. Most tools emit no notifications, so read this only when a result carries `$notifications` or a trailing `$notification:` line.

When an MCP server emits events during a call (progress reporting, server-side
state changes, custom events), mcpx merges them into default structured output
under an injected `$notifications` field:

```yaml
count: 1
$notifications:
- method: notifications/progress
params:
progressToken: '...'
progress: 3
total: 4
message: step 3
```

For non-JSON text, binary, or mixed content, mcpx falls back to the trailing
sentinel line:

```
<tool result lines>
$notification: [{"method":"notifications/progress","params":{...}}]
```

Notifications are objects with these fields:

- `method` — MCP notification method name
- `params` — method-specific payload (shape depends on `method`)
- `aggregatedCount` — only on the **last** `notifications/progress` entry per
progress token; indicates that N intermediate progress entries were collapsed
to keep output bounded. The first and last entries are preserved verbatim.

Notification methods you may encounter:

| method | meaning |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `notifications/progress` | Progress for a long-running tool. Carries `{ progressToken, progress, total?, message? }`. |
| `notifications/tools/list_changed` | Server's tool schema changed. mcpx automatically refreshes the registry; you do not need to react. |
| `$oversize` | Synthetic raw-mode marker. Means notifications were saved to a temp JSON file. Default output renders this as `notifications oversize, saved to <path>`. |
| other (e.g. `notifications/custom/*`) | Passed through verbatim. Server-specific. |

In raw context with a structured result and non-empty notifications, the
trailing sentinel line is replaced by a JSON envelope on stdout:

```json
{ "result": <tool-result>, "notifications": [ ... ] }
```

Text results keep the trailing `$notification:` line even in raw context,
because text content is not JSON and wrapping it would break consumers.

Practical guidance:

- If your task does not depend on progress or server events, ignore notifications.
- Check `$notifications`, the sentinel line, or the raw envelope only when present.
- Do not assume notifications appear; the common case is no `$notification:` at all.
Loading