mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-22 19:56:01 +00:00
394 lines
25 KiB
Markdown
394 lines
25 KiB
Markdown
# pulse-mcp
|
|
|
|
`pulse-mcp` is the Model Context Protocol (MCP) adapter for Pulse
|
|
Intelligence. It exposes the same governed operations substrate that
|
|
Pulse Assistant uses inside the app, but as tools for Claude Desktop,
|
|
Claude Code, OpenCode, and any other MCP-speaking client: list findings,
|
|
read the fleet, drill into a resource, set operator intent, and run
|
|
governed actions.
|
|
|
|
## Pulse Intelligence surface contract
|
|
|
|
<!-- pulse-mcp-surface-contract:start -->
|
|
- **Pulse Intelligence Core**: Canonical context, governed actions, safety gates, approval state, action audit, and verification shared by Pulse Assistant, Pulse MCP, and Pulse Patrol.
|
|
- **Pulse Patrol**: Patrol is the first-party operations surface: it checks infrastructure, investigates issues, follows the chosen Patrol mode before acting, verifies outcomes, and records what happened.
|
|
- **Pulse Assistant** (the native Pulse surface): The contextual explanation, approval, and handoff surface for Patrol findings, governed actions, verification, and operator questions. Affordances: tools and interactive questions.
|
|
- **Pulse MCP**: The external-agent adapter that projects canonical Pulse Intelligence capabilities as MCP tools. Affordances: tools, resources, prompts, and capability metadata.
|
|
<!-- pulse-mcp-surface-contract:end -->
|
|
|
|
The adapter is manifest-driven. Every tool it exposes is one entry in
|
|
Pulse's canonical capabilities manifest at `/api/agent/capabilities`.
|
|
Adding a request/response capability to the manifest-owned Pulse MCP surface
|
|
tool contract extends this server automatically. There is no hardcoded tool
|
|
list to keep in sync, and there is no separate MCP-only action path to maintain
|
|
beside Pulse Assistant. `tools/list` uses the shared Pulse Intelligence
|
|
projection to expose the manifest metadata in each tool description, including
|
|
route, auth scope, action mode (`read`, `mixed`, `write`), approval policy
|
|
(`scope_only`, `action_plan`), request/response shape, and stable error codes.
|
|
Operator-state writes plus governed action and finding lifecycle tools also
|
|
expose typed `inputSchema` definitions for their top-level arguments, including
|
|
stable enums for criticality, approval decisions, and dismissal reasons.
|
|
The same shared projection exposes `resources/list` and `resources/read`
|
|
when the manifest contains the canonical fleet and per-resource context
|
|
capabilities. Resource URIs use `pulse://resource/<resource-id>` and
|
|
`resources/read` returns the same JSON bundle as `get_resource_context`.
|
|
It also exposes `prompts/list` and `prompts/get` from the manifest-owned
|
|
`workflowPrompts` catalogue. Those prompts are workflow hints over the
|
|
manifest tools and resource URIs, not a separate MCP playbook layer.
|
|
|
|
## Install
|
|
|
|
Three ways to get the binary, in order of friction.
|
|
|
|
### 1. One-line installer (recommended)
|
|
|
|
```sh
|
|
curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install-mcp.sh | bash
|
|
```
|
|
|
|
Detects your platform, downloads the matching binary from the latest
|
|
Pulse release, verifies SHA256, and places it at `~/.local/bin/pulse-mcp`
|
|
(or `/usr/local/bin/pulse-mcp` if `~/.local/bin` is not writable).
|
|
Override the install location with `PULSE_MCP_BIN_DIR=/some/path` or
|
|
the version with `PULSE_MCP_VERSION=v6.0.0-rc.5`.
|
|
|
|
Windows (PowerShell):
|
|
|
|
```powershell
|
|
irm https://github.com/rcourtman/Pulse/releases/latest/download/install-mcp.ps1 | iex
|
|
```
|
|
|
|
### 2. Download from a GitHub Release
|
|
|
|
Pick the binary that matches your platform from the Pulse release assets:
|
|
|
|
- `pulse-mcp-darwin-arm64`, `pulse-mcp-darwin-amd64` for macOS
|
|
- `pulse-mcp-linux-amd64`, `pulse-mcp-linux-arm64`, `pulse-mcp-linux-armv7` for Linux
|
|
- `pulse-mcp-windows-amd64.exe` for Windows
|
|
- (full matrix on the release page)
|
|
|
|
`chmod +x` it on Unix, drop it on your `PATH`. SHA256 sums for every
|
|
binary are in `checksums.txt` on the same release.
|
|
|
|
On macOS the first launch may show a Gatekeeper warning ("cannot be
|
|
opened because the developer cannot be verified"). Either right-click
|
|
the binary and pick Open the first time, or run
|
|
`xattr -d com.apple.quarantine pulse-mcp` to clear the quarantine flag.
|
|
Notarization is intentionally skipped for v1; the install-script path
|
|
above downloads the same unsigned binary.
|
|
|
|
### 3. Build from source
|
|
|
|
If you have Go installed:
|
|
|
|
```sh
|
|
go install github.com/rcourtman/pulse-go-rewrite/cmd/pulse-mcp@latest
|
|
```
|
|
|
|
Or from a Pulse repo checkout:
|
|
|
|
```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).
|
|
|
|
## Quick start
|
|
|
|
### 1. Get the binary
|
|
|
|
Use any of the install paths above. The rest of this guide assumes
|
|
`pulse-mcp` is on your `PATH`.
|
|
|
|
### 2. Mint an API token
|
|
|
|
Pulse fetches the live capabilities manifest first, and every tool
|
|
description includes its required auth scope. For a read-only external
|
|
agent, start with `monitoring:read`. For the full published Pulse
|
|
Intelligence surface, mint a token with the current manifest scopes:
|
|
<!-- pulse-mcp-scope-list:start -->
|
|
`monitoring:read`, `monitoring:write`, `settings:read`, `settings:write`, `ai:execute`, `actions:plan`, `actions:approve`, and `actions:execute`
|
|
<!-- pulse-mcp-scope-list:end -->
|
|
|
|
You can also mint narrower tokens for specific workflows. For example,
|
|
omit `settings:write` if the client should not add or edit monitored
|
|
infrastructure sources, and omit `ai:execute` if it should not review
|
|
Patrol findings or plan, approve, or execute governed actions. Mint the
|
|
token in **Settings →
|
|
Security → API Tokens**.
|
|
|
|
### 3. Wire it into your client
|
|
|
|
<!-- pulse-mcp-client-config:start -->
|
|
Most MCP clients need the same manifest-owned runtime facts: server name `pulse`, command `pulse-mcp`, base URL flag `--base-url`, default URL `http://localhost:7655`, and token environment variable `PULSE_API_TOKEN`.
|
|
The generated examples below cover the currently declared config families: `OpenCode`, `Claude-style clients`, and `custom MCP clients`.
|
|
If your client uses a different outer config format, keep those runtime facts and adapt only the wrapper.
|
|
|
|
#### OpenCode
|
|
|
|
Uses OpenCode's top-level mcp object.
|
|
Add this to `opencode.json`, `opencode.jsonc`, and `~/.config/opencode/opencode.json`:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"mcp": {
|
|
"pulse": {
|
|
"type": "local",
|
|
"command": ["pulse-mcp", "--base-url", "http://localhost:7655"],
|
|
"enabled": true,
|
|
"environment": {
|
|
"PULSE_API_TOKEN": "your-token-here"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Restart OpenCode or reload its config after saving. Pulse tools appear under the `pulse` MCP server name.
|
|
|
|
#### Claude-style clients
|
|
|
|
Uses the common mcpServers object supported by Claude Desktop and Claude Code.
|
|
Use this shape for `Claude Desktop` and `Claude Code` in `~/Library/Application Support/Claude/claude_desktop_config.json` and `.mcp.json`:
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"pulse": {
|
|
"command": "pulse-mcp",
|
|
"args": ["--base-url", "http://localhost:7655"],
|
|
"env": {
|
|
"PULSE_API_TOKEN": "your-token-here"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Restart your client after saving the config. If the client cannot resolve `pulse-mcp` from `PATH`, use the full binary path.
|
|
|
|
#### custom MCP clients
|
|
|
|
Keeps the Pulse MCP command, base URL flag, and token environment variable while adapting the outer client config shape.
|
|
Use command `pulse-mcp`, pass `--base-url http://localhost:7655`, set `PULSE_API_TOKEN` to the API token, and keep the server name `pulse` when the client asks for one.
|
|
<!-- pulse-mcp-client-config:end -->
|
|
|
|
## 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 |
|
|
| `--emit-notifications` | `false` | Translate Pulse SSE events into JSON-RPC notifications on stdout |
|
|
|
|
The token is always read from an environment variable, never a flag, so
|
|
it does not appear in process listings.
|
|
|
|
### About `--emit-notifications`
|
|
|
|
When enabled, the server opens a long-lived connection to
|
|
`/api/agent/events` after `initialize` and writes a JSON-RPC
|
|
notification on stdout for every non-transport event:
|
|
|
|
```json
|
|
{ "jsonrpc": "2.0", "method": "notifications/finding.created", "params": { ... } }
|
|
{ "jsonrpc": "2.0", "method": "notifications/approval.pending", "params": { ... } }
|
|
{ "jsonrpc": "2.0", "method": "notifications/action.completed", "params": { ... } }
|
|
```
|
|
|
|
The `params` object is the SSE event's `data` payload verbatim, so an
|
|
agent that already knows the substrate's wire shape sees identical
|
|
content to what an HTTP SSE consumer would. Transport plumbing
|
|
(`stream.connected`, `heartbeat`) is filtered out.
|
|
|
|
It is off by default because not every MCP client surfaces
|
|
server-initiated notifications. Enable it when wiring an autonomous
|
|
agent that processes the JSON-RPC stream programmatically. Claude
|
|
Desktop today does not surface arbitrary `notifications/*` methods
|
|
in the chat UI; if your client falls in that category, leave the
|
|
flag off and consume the SSE stream directly.
|
|
|
|
When `--emit-notifications` is on, the `initialize` response
|
|
advertises the supported event kinds under
|
|
`capabilities.experimental.pulseNotifications.kinds`. That list is
|
|
projected from the shared Pulse Intelligence event vocabulary, so it
|
|
stays aligned with `/api/agent/events`. Clients that don't understand
|
|
the experimental block ignore it silently.
|
|
|
|
## What the tools do
|
|
|
|
The exact request/response tool set is whatever your Pulse instance's
|
|
manifest declares. The generated inventory below reflects the canonical
|
|
manifest in this checkout; run `tools/list` from your MCP client to see
|
|
the live set.
|
|
|
|
<!-- pulse-mcp-tools:start -->
|
|
**Context (read-only):**
|
|
|
|
- `get_resource_context` (Get resource context, `GET /api/agent/resource-context/{resourceId}`, scope `monitoring:read`, mode `read`, approval `scope_only`): Return the situated picture of a resource — identity, operator-set state with maintenance-window-active flag, active findings, pending approvals scoped to this resource, recent actions including refused dispatches. Command fields are redacted for monitoring-read API tokens unless the token also has ai:execute.
|
|
- `list_resource_capabilities` (List resource capabilities, `GET /api/agent/resource-capabilities/{resourceId}`, scope `monitoring:read`, mode `read`, approval `scope_only`): Return the structured governed capabilities a resource advertises (name, type, approval level, platform, and full parameter schemas). Companion to get_resource_context, which renders capabilities as count-limited prose; this is the structured surface an agent reads before calling plan_action so it can populate capabilityName and params without guessing. A resource with no advertised capabilities returns an empty array.
|
|
- `get_fleet_context` (Get fleet context, `GET /api/agent/fleet-context`, scope `monitoring:read`, mode `read`, approval `scope_only`): Return a thin per-resource triage rollup across every resource visible to the org — identity, operator flags (intentionallyOffline, neverAutoRemediate, maintenanceWindowActive), per-severity finding counts (total/critical/warning/info), and pending-approval count. One read for 'where do I focus?'; follow up via get_resource_context for depth. Optional additive filters (hasFindings, severity, technology, resourceType) narrow the result to a relevant subset so agents triaging a large fleet do not receive or page through healthy resources.
|
|
- `get_patrol_control_status` (Get Patrol work status, `GET /api/agent/patrol-control/status`, scope `monitoring:read`, mode `read`, approval `scope_only`): Return the current content-safe Patrol work status: Patrol issue evidence, pending approvals, governed decisions/actions, verified outcomes, Patrol control outcome evidence, compatibility aliases, and optional MCP readiness. The payload is count-only and deliberately omits finding ids, action ids, prompts, commands, resource names, and output.
|
|
|
|
**Operator state (per-resource intent):**
|
|
|
|
- `get_operator_state` (Get operator state, `GET /api/resources/{resourceId}/operator-state`, scope `monitoring:read`, mode `read`, approval `scope_only`): Read the operator-set state for a resource (intentionally offline, never auto-remediate, maintenance window, criticality).
|
|
- `set_operator_state` (Set operator state, `PUT /api/resources/{resourceId}/operator-state`, scope `monitoring:write`, mode `write`, approval `scope_only`): Replace the operator-set state for a resource. URL canonicalId wins over body; server populates setAt and setBy from the authenticated identity.
|
|
- `clear_operator_state` (Clear operator state, `DELETE /api/resources/{resourceId}/operator-state`, scope `monitoring:write`, mode `write`, approval `scope_only`): Remove any operator-set state for a resource. Idempotent — succeeds whether or not an entry was present.
|
|
|
|
**Findings (Patrol lifecycle):**
|
|
|
|
- `list_findings` (List findings, `GET /api/ai/patrol/findings`, scope `ai:execute`, mode `read`, approval `scope_only`): List all Patrol findings (active, dismissed, resolved). Filter client-side on returned shape.
|
|
- `acknowledge_finding` (Acknowledge finding, `POST /api/ai/patrol/acknowledge`, scope `ai:execute`, mode `write`, approval `scope_only`): Mark a finding as seen but keep it visible. Auto-resolves when the underlying condition clears.
|
|
- `snooze_finding` (Snooze finding, `POST /api/ai/patrol/snooze`, scope `ai:execute`, mode `write`, approval `scope_only`): Hide a finding for a defined duration in hours.
|
|
- `dismiss_finding` (Dismiss finding, `POST /api/ai/patrol/dismiss`, scope `ai:execute`, mode `write`, approval `scope_only`): Dismiss a finding with a reason: not_an_issue (permanent suppression), expected_behavior (acknowledged forever), or will_fix_later (7-day reminder commitment).
|
|
- `resolve_finding` (Resolve finding, `POST /api/ai/patrol/resolve`, scope `ai:execute`, mode `write`, approval `scope_only`): Manually mark a finding as resolved when the underlying issue has been fixed out-of-band.
|
|
|
|
**Actions (governed plan/approval/execute):**
|
|
|
|
- `plan_action` (Plan action, `POST /api/actions/plan`, scope `actions:plan`, mode `write`, approval `action_plan`): Plan an action against a resource. The planner validates the request, looks up the capability on the resource, checks executor-owned live availability, and returns an ActionPlan with the approval policy, blast radius, plan hash, and preflight summary. The plan is persisted to the audit history at the planned/pending state only after the live availability check passes, so subsequent decide_action and execute_action calls can reference it by id. Plan-and-execute is a two-step flow when the resulting plan requires approval, one-step otherwise.
|
|
- `decide_action` (Decide action, `POST /api/actions/{actionId}/decision`, scope `actions:approve`, mode `write`, approval `action_plan`): Record an approval decision (approved or rejected) on a previously planned action. The actor is taken from the authenticated identity; an explicit reason can be passed in the body. An exact retry returns the authoritative persisted decision without adding an approval or lifecycle event; a conflicting retry fails closed.
|
|
- `execute_action` (Execute action, `POST /api/actions/{actionId}/execute`, scope `actions:execute`, mode `write`, approval `action_plan`): Execute a previously planned and (when required) approved action. Returns the persisted audit record with the execution result attached. Refuses with stable codes when the action is in the wrong lifecycle state (action_not_approved, action_already_executing, action_execution_final, action_dry_run_only, action_plan_expired), when the approved plan no longer matches the current resource/capability contract (action_plan_drift), when the target is operator-locked against automated remediation (resource_remediation_locked), or when the API instance has no executor wired (action_executor_unavailable). action.completed SSE events fire on every terminal state so agents watching the stream do not need to poll this endpoint after dispatch.
|
|
|
|
**Provisioning (infrastructure onboarding):**
|
|
|
|
- `list_nodes` (List nodes, `GET /api/config/nodes`, scope `settings:read`, mode `read`, approval `scope_only`): List configured infrastructure sources that Pulse can monitor or manage. Credential secret values are redacted; use the returned id with update_node, remove_node, test_node_connection, or refresh_node_cluster_membership.
|
|
- `add_node` (Add node, `POST /api/config/nodes`, scope `settings:write`, mode `write`, approval `scope_only`): Add a Proxmox VE, Proxmox Backup Server, or Proxmox Mail Gateway source to Pulse after credentials have been collected, generated, or approved.
|
|
- `update_node` (Update node, `PUT /api/config/nodes/{nodeId}`, scope `settings:write`, mode `write`, approval `scope_only`): Update a configured infrastructure source. Omitted fields preserve the current value; tokenValue or password only changes when supplied.
|
|
- `remove_node` (Remove node, `DELETE /api/config/nodes/{nodeId}`, scope `settings:write`, mode `write`, approval `scope_only`): Remove a configured infrastructure source from Pulse by node id.
|
|
- `test_node_credentials` (Test node credentials, `POST /api/config/nodes/test-config`, scope `settings:write`, mode `mixed`, approval `scope_only`): Validate proposed source credentials and connection details without saving them to Pulse.
|
|
- `test_node_connection` (Test node connection, `POST /api/config/nodes/{nodeId}/test`, scope `settings:write`, mode `mixed`, approval `scope_only`): Validate the saved connection for an existing configured infrastructure source.
|
|
- `refresh_node_cluster_membership` (Refresh node cluster membership, `POST /api/config/nodes/{nodeId}/refresh-cluster`, scope `settings:write`, mode `write`, approval `scope_only`): Re-detect Proxmox VE cluster membership and endpoint metadata for a configured source.
|
|
- `discover_lan` (Discover LAN, `POST /api/discover`, scope `settings:write`, mode `mixed`, approval `scope_only`): Scan a subnet, or return cached scan results, to find candidate infrastructure hosts before deciding which sources to add to Pulse.
|
|
<!-- pulse-mcp-tools:end -->
|
|
|
|
## Workflow prompts
|
|
|
|
Clients that support MCP prompts can also discover Pulse workflow hints.
|
|
They are projected from the manifest-owned `workflowPrompts` catalogue:
|
|
|
|
<!-- pulse-mcp-prompts:start -->
|
|
- `pulse_triage_fleet` (Triage fleet): Triage the Pulse fleet using the canonical fleet context capability, then choose where deeper investigation is warranted.
|
|
- `pulse_operations_loop` (Ask Patrol to handle an issue): Have Patrol investigate active findings, follow the configured Patrol mode, take approved actions, verify the outcome, and record what happened.
|
|
- `pulse_investigate_resource` (Investigate resource; required argument `resourceId`): Investigate one Pulse resource using the canonical resource context capability and resource URI projection.
|
|
- `pulse_review_finding` (Review finding; required argument `finding_id`): Review one Patrol finding and propose the safest governed next step.
|
|
<!-- pulse-mcp-prompts:end -->
|
|
|
|
Run `prompts/list` from your MCP client to see the live prompt set.
|
|
|
|
## Resource browser
|
|
|
|
Clients that support MCP resources can call `resources/list` to browse
|
|
the same canonical resource IDs returned by `get_fleet_context`, then
|
|
`resources/read` to fetch the full `get_resource_context` JSON bundle
|
|
for one resource.
|
|
|
|
Example URI:
|
|
|
|
```text
|
|
pulse://resource/vm:101
|
|
```
|
|
|
|
The adapter does not maintain a separate resource registry. The resource
|
|
list is rebuilt through the live fleet-context capability, and each read
|
|
is a normal per-resource context capability call with the same auth scope,
|
|
redaction rules, and error envelope as the tool surface.
|
|
|
|
## 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 stable codes are advertised by the manifest:
|
|
|
|
<!-- pulse-mcp-errors:start -->
|
|
- `get_resource_context`: `resource_not_found`
|
|
- `list_resource_capabilities`: `resource_not_found`
|
|
- `get_operator_state`: `operator_state_not_set`
|
|
- `set_operator_state`: `operator_state_invalid`
|
|
- `acknowledge_finding`: `invalid_finding_request`, `finding_not_found`, `finding_action_not_allowed`, and `patrol_unavailable`
|
|
- `snooze_finding`: `invalid_finding_request`, `finding_not_found`, `finding_action_not_allowed`, and `patrol_unavailable`
|
|
- `dismiss_finding`: `invalid_finding_request`, `finding_not_found`, `finding_action_not_allowed`, and `patrol_unavailable`
|
|
- `resolve_finding`: `invalid_finding_request`, `finding_not_found`, `finding_action_not_allowed`, and `patrol_unavailable`
|
|
- `plan_action`: `invalid_action_request`, `mock_mode_enabled`, `action_actor_unavailable`, `resource_not_found`, `capability_not_found`, and `action_execution_unavailable`
|
|
- `decide_action`: `mock_mode_enabled`, `missing_id`, `invalid_id`, `invalid_action_decision`, `action_not_found`, `action_not_pending`, `action_plan_expired`, `action_plan_identity_mismatch`, `action_actor_unavailable`, `action_approval_forbidden`, `action_step_up_unavailable`, `action_decision_conflict`, `action_separation_required`, and `action_replan_required`
|
|
- `execute_action`: `mock_mode_enabled`, `missing_id`, `invalid_id`, `invalid_action_execution`, `action_not_found`, `action_not_approved`, `action_already_executing`, `action_execution_final`, `action_dry_run_only`, `action_plan_expired`, `action_plan_drift`, `action_plan_identity_mismatch`, `resource_remediation_locked`, `action_executor_unavailable`, `action_actor_unavailable`, `action_execution_forbidden`, `action_not_executing`, and `action_replan_required`
|
|
<!-- pulse-mcp-errors:end -->
|
|
|
|
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` tool.** SSE streaming does not fit the MCP
|
|
request/response tool shape, so the adapter does not expose the
|
|
`/api/agent/events` stream as a callable tool. Agents that want
|
|
real-time push have two options: consume the SSE stream directly
|
|
via HTTP (works with any MCP client), or run with
|
|
`--emit-notifications` so the bridge translates SSE events into
|
|
JSON-RPC notifications on the stdio channel (requires a client
|
|
that processes server-initiated 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.
|
|
|
|
## 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 a write or action tool returns 403 access_denied.**
|
|
Your token is missing that tool's required scope. Run `tools/list` and
|
|
check the `required scope` line in the tool description. Operator-state
|
|
writes need `monitoring:write`, provisioning tools need `settings:read`
|
|
or `settings:write`, and Patrol finding plus governed action tools need
|
|
`ai:execute`. Mint a narrower or broader token depending on which
|
|
external-agent workflows you want to allow.
|
|
|
|
**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 binary is intentionally thin. 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 delegates method semantics, tool projection,
|
|
call-argument normalization, HTTP projection, and result envelopes to the
|
|
shared Pulse Intelligence contracts in `internal/agentcapabilities`.
|
|
Tool input schemas come from the manifest-owned JSON Schema when present;
|
|
the shared projection only falls back to deriving path placeholders and a
|
|
permissive body object for capabilities that have not yet authored a
|
|
stricter schema.
|
|
|
|
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
|
|
Pulse Intelligence's external-agent profiles: stdio MCP and direct HTTP
|
|
API. Pulse Assistant remains the first-party in-app surface over the
|
|
same governed contracts; `pulse-mcp` is the adapter for users who prefer
|
|
an external agent host.
|