mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Publish the cmd/pulse-mcp integration guide
Slice 51 added the MCP adapter as a worked example. This makes it a published surface: an external maintainer who wants to wire Pulse into their Claude Desktop or Claude Code can read one README and have it working without spelunking through main.go. The guide carries: - Build and install instructions - Canonical config snippets for Claude Desktop and Claude Code - The env-var contract (PULSE_API_TOKEN, configurable name, always read from env so it stays out of process listings) - The published tool list grouped by category (context, operator-state, finding) with what each does - The stable error envelope shape and the difference between capability-specific codes and cross-cutting auth codes - Documented limitations: no subscribe_events, manifest fetched once, tools-only (no resource URIs) - Troubleshooting for the common failure modes (missing token, proxy gating discovery, missing write scope) api-contracts.md now points readers at the README as the canonical integration entry point so the contract doc keeps its in-repo focus and the README owns the user-facing copy.
This commit is contained in:
parent
7e4e3f03ce
commit
3a502fefda
2 changed files with 206 additions and 0 deletions
197
cmd/pulse-mcp/README.md
Normal file
197
cmd/pulse-mcp/README.md
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
# pulse-mcp
|
||||
|
||||
`pulse-mcp` is a Model Context Protocol (MCP) server that wraps Pulse's
|
||||
agent surface as a tool set. It lets Claude Desktop, Claude Code, and any
|
||||
other MCP-speaking client drive Pulse natively: list findings, read the
|
||||
fleet, drill into a resource, set operator intent, run governed actions.
|
||||
|
||||
The adapter is manifest-driven. Every tool it exposes is one entry in
|
||||
Pulse's hand-authored capabilities manifest at `/api/agent/capabilities`.
|
||||
Adding a capability there extends this server automatically. There is no
|
||||
hardcoded tool list to keep in sync.
|
||||
|
||||
## Quick start
|
||||
|
||||
### 1. Build
|
||||
|
||||
From the repo root:
|
||||
|
||||
```sh
|
||||
go build -o pulse-mcp ./cmd/pulse-mcp
|
||||
```
|
||||
|
||||
Drop the binary somewhere on your `PATH` (or reference its full path in
|
||||
the config snippets below).
|
||||
|
||||
### 2. Mint an API token
|
||||
|
||||
Pulse needs a token with `monitoring:read` for the read tools, and
|
||||
`monitoring:write` if you want the write tools (`set_operator_state`,
|
||||
`clear_operator_state`) to work. Mint one in **Settings → Security → API
|
||||
Tokens**.
|
||||
|
||||
### 3. Wire it into your client
|
||||
|
||||
#### Claude Desktop
|
||||
|
||||
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
(macOS) or the platform equivalent. Add a `pulse` entry under
|
||||
`mcpServers`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"pulse": {
|
||||
"command": "/usr/local/bin/pulse-mcp",
|
||||
"args": ["--base-url", "http://localhost:7655"],
|
||||
"env": {
|
||||
"PULSE_API_TOKEN": "your-token-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Restart Claude Desktop. The Pulse tools appear in the tool picker.
|
||||
|
||||
#### Claude Code
|
||||
|
||||
Add the server to your project's `.mcp.json` (or your user-level config):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"pulse": {
|
||||
"command": "pulse-mcp",
|
||||
"args": ["--base-url", "http://localhost:7655"],
|
||||
"env": {
|
||||
"PULSE_API_TOKEN": "your-token-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Flag | Default | Purpose |
|
||||
| ---- | ------- | ------- |
|
||||
| `--base-url` | `http://localhost:7655` | Pulse instance to talk to |
|
||||
| `--token-env` | `PULSE_API_TOKEN` | Env var holding the API token |
|
||||
|
||||
The token is always read from an environment variable, never a flag, so
|
||||
it does not appear in process listings.
|
||||
|
||||
## What the tools do
|
||||
|
||||
The exact set is whatever your Pulse instance's manifest declares. As of
|
||||
this writing, the published capabilities are:
|
||||
|
||||
**Context (read-only):**
|
||||
|
||||
- `get_resource_context` returns the situated picture of one resource:
|
||||
identity, operator-set state, active findings, pending approvals,
|
||||
recent actions including refused dispatches with their stable error
|
||||
tokens.
|
||||
- `get_fleet_context` returns a thin per-resource rollup across the org
|
||||
for triage: identity, operator flags, per-severity finding counts,
|
||||
pending-approval count.
|
||||
|
||||
**Operator state (per-resource intent):**
|
||||
|
||||
- `get_operator_state` reads the operator-set state for a resource
|
||||
(intentionally offline, never-auto-remediate, maintenance window,
|
||||
criticality).
|
||||
- `set_operator_state` replaces the entire record. The server populates
|
||||
attribution (`setAt`, `setBy`) so client values cannot spoof it.
|
||||
- `clear_operator_state` removes the entry. Idempotent.
|
||||
|
||||
**Findings (Patrol lifecycle):**
|
||||
|
||||
- `list_findings` returns every Patrol finding (active, dismissed,
|
||||
resolved). Filter client-side.
|
||||
- `acknowledge_finding` marks a finding as seen but keeps it visible.
|
||||
- `snooze_finding` hides a finding for a duration in hours.
|
||||
- `dismiss_finding` permanently dismisses a finding with a reason
|
||||
(`not_an_issue`, `expected_behavior`, `will_fix_later`).
|
||||
- `resolve_finding` manually marks a finding resolved.
|
||||
|
||||
Run `tools/list` from your MCP client to see the live set.
|
||||
|
||||
## Stable error envelope
|
||||
|
||||
Every Pulse error reaches your agent verbatim, in this shape:
|
||||
|
||||
```json
|
||||
{ "error": "<stable_code>", "message": "<human readable>" }
|
||||
```
|
||||
|
||||
The MCP tool result wraps that JSON in a text content block with
|
||||
`isError: true`. Branch on `error` (snake_case stable codes); use
|
||||
`message` for surfacing to humans, never for branching.
|
||||
|
||||
Capability-specific codes the substrate currently emits include
|
||||
`resource_not_found` (depth read on an unknown id),
|
||||
`operator_state_not_set` (read on a resource with no operator entry), and
|
||||
`operator_state_invalid` (write rejected by the validator). Cross-cutting
|
||||
codes from the auth / multi-tenant middleware (`invalid_org`,
|
||||
`org_suspended`, `access_denied`) can apply to any authenticated tool
|
||||
call.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **No `subscribe_events`.** SSE streaming does not fit the MCP
|
||||
request/response tool shape, so the adapter does not expose the
|
||||
`/api/agent/events` stream. Agents that need real-time push (finding
|
||||
created, approval pending, action completed) consume the SSE stream
|
||||
directly via HTTP. A future version may layer this onto MCP
|
||||
notifications.
|
||||
|
||||
- **Manifest is fetched once.** The server fetches `/api/agent/
|
||||
capabilities` at startup and does not refresh during the process
|
||||
lifetime. Restart `pulse-mcp` to pick up new capabilities after
|
||||
upgrading Pulse.
|
||||
|
||||
- **No resource URIs.** MCP supports a `resources/` channel in addition
|
||||
to `tools/`. The adapter exposes only tools today; this is sufficient
|
||||
for the substrate's current surface and keeps the adapter small.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"env var PULSE_API_TOKEN is empty" on startup.**
|
||||
The adapter refuses to start without a token. Mint one in Settings and
|
||||
make sure your client's `env` block (or shell environment) sets
|
||||
`PULSE_API_TOKEN`.
|
||||
|
||||
**"manifest GET returned 401" on startup.**
|
||||
Discovery is supposed to be unauthenticated. If your Pulse instance is
|
||||
behind a reverse proxy that adds auth in front of the public paths,
|
||||
the proxy is gating the manifest endpoint. Make sure
|
||||
`/api/agent/capabilities` is reachable without a credential, the same
|
||||
way `/api/health` is.
|
||||
|
||||
**Tools work, but `set_operator_state` returns 403 access_denied.**
|
||||
Your token is missing `monitoring:write`. Either mint a new token with
|
||||
both scopes, or restrict yourself to the read tools.
|
||||
|
||||
**Tools list is empty.**
|
||||
The adapter filters `subscribe_events` out (it is not request/response
|
||||
shaped). If literally nothing else shows up, your Pulse instance's
|
||||
manifest is empty, which is a Pulse-side bug; check
|
||||
`curl http://your-pulse/api/agent/capabilities` directly.
|
||||
|
||||
## Implementation notes
|
||||
|
||||
The adapter is one stdlib-only Go file (`main.go`) in around 430 lines.
|
||||
It speaks JSON-RPC 2.0 over stdio with line-delimited framing, logs to
|
||||
stderr to keep the JSON-RPC channel on stdout clean, and derives each
|
||||
tool's input schema from the manifest entry: `{name}` segments in the
|
||||
declared path become required string properties, and non-GET/DELETE
|
||||
tools accept a free-form `body` object.
|
||||
|
||||
The companion worked example, `cmd/agent-probe`, walks the same
|
||||
substrate as a plain HTTP client and is a useful reference for anyone
|
||||
building a non-MCP integration. Together the two binaries demonstrate
|
||||
the substrate's two consumer profiles: stdio MCP and HTTP API. Both are
|
||||
manifest-driven and stdlib-only, so adding capabilities to Pulse extends
|
||||
both without code changes here.
|
||||
|
|
@ -1517,6 +1517,15 @@ substrate's stable error envelope (`{"error": "code", "message":
|
|||
"..."}`) verbatim through MCP's content-and-isError result so
|
||||
agents on the MCP side branch on the same stable codes.
|
||||
|
||||
The integration guide for `cmd/pulse-mcp` lives at
|
||||
`cmd/pulse-mcp/README.md`. It carries the canonical Claude Desktop
|
||||
and Claude Code config snippets, the env-var contract for the API
|
||||
token, the published list of tools the manifest currently
|
||||
exposes, and the documented limitations (no `subscribe_events`,
|
||||
manifest fetched once at startup). External maintainers wiring
|
||||
Pulse into their MCP-speaking client read that document, not the
|
||||
package source.
|
||||
|
||||
`/api/agent/resource-context/{id}` is the agent-consumable bundled
|
||||
context endpoint. One read returns the full situated picture of a
|
||||
resource — identity, operator-set state (with server-computed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue