docs: update README for plugin system, add proxyModel tests

- Rewrite README Content Pipeline section as Plugin System section
  documenting built-in plugins (default, gate, content-pipeline),
  plugin hooks, and the relationship between gating and proxyModel
- Update all README examples to use --proxy-model instead of --gated
- Add unit tests: proxyModel normalization in JSON/YAML output (4 tests),
  Plugin Config section in describe output (2 tests)
- Add smoke tests: yaml/json output shows resolved proxyModel without
  gated field, round-trip compatibility (4 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michal
2026-03-07 01:24:47 +00:00
parent f60d40a25b
commit d9d0a7a374
4 changed files with 224 additions and 40 deletions

113
README.md
View File

@@ -21,7 +21,7 @@ Claude Code <--STDIO--> mcplocal (local proxy) <--HTTP--> mcpd (daemon) <--
```
- **mcpd** — the daemon. Runs on a server, manages MCP server containers (Docker/Podman), stores configuration in PostgreSQL.
- **mcplocal** — local proxy. Runs on your machine, presents a single MCP endpoint to Claude that merges tools from all your servers. Handles namespacing (`grafana/search_dashboards`), gated sessions, and prompt delivery.
- **mcplocal** — local proxy. Runs on your machine, presents a single MCP endpoint to Claude that merges tools from all your servers. Handles namespacing (`grafana/search_dashboards`), plugin execution (gating, content pipelines), and prompt delivery.
- **mcpctl** — the CLI. Talks to mcpd (via mcplocal or directly) to manage everything.
## Quick Start
@@ -88,7 +88,7 @@ A project groups servers together and configures how Claude interacts with them.
mcpctl create project monitoring \
--description "Grafana dashboards and alerting" \
--server grafana \
--no-gated
--proxy-model content-pipeline
```
### 6. Connect Claude Code
@@ -133,7 +133,7 @@ servers:
projects:
- name: monitoring
description: "Infrastructure monitoring"
gated: false
proxyModel: content-pipeline
servers:
- grafana
```
@@ -150,55 +150,80 @@ mcpctl get all --project monitoring -o yaml > backup.yaml
mcpctl apply -f backup.yaml
```
## Content Pipeline (ProxyModel)
## Plugin System (ProxyModel)
ProxyModel defines a **content transformation pipeline** that runs between upstream MCP servers and the client (e.g., Claude). It processes tool results, prompts, and resources through ordered stages before delivering them.
ProxyModel is mcpctl's plugin system. Each project is assigned a **plugin** that controls how Claude interacts with its servers. Plugins are composed from two layers: **TypeScript plugins** (MCP middleware hooks) and **YAML pipelines** (content transformation stages).
### Built-in Models
### Built-in Plugins
| Model | Stages | Use case |
|-------|--------|----------|
| Plugin | Includes gating | Content pipeline | Description |
|--------|:-:|:-:|---|
| **default** | Yes | Yes | Gate + content pipeline. The default for all projects. |
| **gate** | Yes | No | Gating only — `begin_session` gate with prompt delivery. |
| **content-pipeline** | No | No | Content transformation only — no gating. |
**Gating** means Claude initially sees only a `begin_session` tool. After calling it with a task description, relevant prompts are delivered and the full tool list is revealed. This keeps Claude's context focused.
```bash
# Create a gated project (default behavior)
mcpctl create project home --server my-ha --proxy-model default
# Create an ungated project (direct tool access, no gate)
mcpctl create project tools --server grafana --proxy-model content-pipeline
```
### Plugin Hooks
TypeScript plugins intercept MCP requests/responses at specific lifecycle points:
| Hook | When it fires |
|------|--------------|
| `onSessionCreate` | New MCP session established |
| `onSessionDestroy` | Session ends |
| `onInitialize` | MCP `initialize` request — can inject instructions |
| `onToolsList` | `tools/list` — can filter/modify tool list |
| `onToolCallBefore` | Before forwarding a tool call — can intercept |
| `onToolCallAfter` | After receiving tool result — can transform |
| `onResourcesList` | `resources/list` — can filter resources |
| `onResourceRead` | `resources/read` — can intercept resource reads |
| `onPromptsList` | `prompts/list` — can filter prompts |
| `onPromptGet` | `prompts/get` — can intercept prompt reads |
Plugins compose via `extends` — the `default` plugin extends both `gate` and `content-pipeline`, inheriting all their hooks.
### Content Pipelines
Content pipelines transform tool results through ordered stages before delivering to Claude:
| Pipeline | Stages | Use case |
|----------|--------|----------|
| **default** | `passthrough``paginate` (8KB pages) | Safe pass-through with pagination for large responses |
| **subindex** | `section-split``summarize-tree` | Splits large content into sections, returns a summary index. Client drills down with `_resultId`/`_section` params |
| **subindex** | `section-split``summarize-tree` | Splits large content into sections, returns a summary index |
### How `subindex` Works
#### How `subindex` Works
1. Upstream returns a large tool result (e.g., 50KB of device states)
2. `section-split` divides content into logical sections (2KB15KB each)
2. `section-split` divides content into logical sections (2KB-15KB each)
3. `summarize-tree` generates a compact index with section summaries (~200 tokens each)
4. Client receives the index and can request specific sections via `_section` parameter
### Configuration
Set per-project (all servers use the same model):
Set per-project:
```yaml
kind: Project
metadata:
name: home-automation
spec:
servers: [home-assistant, node-red]
proxyModel: subindex
```
Override per-server within a project:
```yaml
kind: Project
metadata:
name: monitoring
spec:
servers: [grafana, prometheus]
proxyModel: default
serverOverrides:
grafana:
proxyModel: subindex
kind: project
name: home-automation
proxyModel: default
servers:
- home-assistant
- node-red
```
Via CLI:
```bash
mcpctl create project monitoring --server grafana --server prometheus --proxy-model subindex
mcpctl create project monitoring --server grafana --proxy-model content-pipeline
```
### Custom ProxyModels
@@ -223,7 +248,13 @@ spec:
cacheable: true
```
Inspect available models: `mcpctl get proxymodels` / `mcpctl describe proxymodel subindex`
Inspect available plugins and pipelines:
```bash
mcpctl get proxymodels # List all plugins and pipelines
mcpctl describe proxymodel default # Pipeline details (stages, controller)
mcpctl describe proxymodel gate # Plugin details (hooks, extends)
```
## Resources
@@ -255,12 +286,12 @@ mcpctl describe project monitoring
# Create resources
mcpctl create server <name> [flags]
mcpctl create secret <name> --data KEY=value
mcpctl create project <name> --server <srv> [--gated]
mcpctl create project <name> --server <srv> [--proxy-model <plugin>]
mcpctl create prompt <name> --project <proj> --content "..."
# Modify resources
mcpctl edit server grafana # Opens in $EDITOR
mcpctl patch project myproj gated=true
mcpctl patch project myproj proxyModel=default
mcpctl apply -f config.yaml # Declarative create/update
# Delete resources
@@ -300,7 +331,7 @@ mcpctl create server my-ha \
## Gated Sessions
Projects are **gated** by default. When Claude connects to a gated project:
Projects using the `default` or `gate` plugin are **gated**. When Claude connects to a gated project:
1. Claude sees only a `begin_session` tool initially
2. Claude calls `begin_session` with a description of its task
@@ -310,9 +341,11 @@ Projects are **gated** by default. When Claude connects to a gated project:
This keeps Claude's context focused — instead of dumping 100+ tools and pages of docs upfront, only the relevant ones are delivered based on the task at hand.
```bash
# Enable/disable gating
mcpctl patch project monitoring gated=true
mcpctl patch project monitoring gated=false
# Gated (default)
mcpctl create project monitoring --server grafana --proxy-model default
# Ungated (direct tool access)
mcpctl create project tools --server grafana --proxy-model content-pipeline
```
## Prompts