Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

pulse mcp

Audience: operators wiring Pulse into an MCP-aware AI client (Claude Desktop, Claude Code, generic MCP clients).

pulse mcp runs the Model Context Protocol server over stdio. The AI client launches pulse mcp as a subprocess, speaks MCP over its stdio streams, and shuts it down on session close.

LLM agents using MCP: the agent-side guide is the session-bootstrap skill — fetch it via pulse_skills_get for the tool catalog and request shapes. This page is for the human setting the server up.

Synopsis

pulse mcp [--data-dir PATH] [--feature-profile FILE] [--return PRESET] [--limit NAME=VALUE]... [--bind-on-open] [--no-cohort-scan] [--metrics-addr HOST:PORT] [--log-level LEVEL] [--log-format text|json]

The command reads stdin, writes MCP responses on stdout, and writes a one-line startup notice (and any subsequent diagnostics, including --log-level records) on stderr. Nothing but JSON-RPC is ever written to stdout.

Flags

FlagTypeDefaultPurpose
--data-dirstringfrom PULSE_DATA_DIR env varCohort base directory
--bind-on-openbooltrueRegister session-scoped JSON-schema-bound tool variants on successful pulse_inspect
--no-cohort-scanboolfalseSkip the startup walk that enumerates .pulse files as pulse:// resources (env: PULSE_MCP_NO_COHORT_SCAN)
--feature-profilestringfrom PULSE_FEATURE_PROFILE env varFeature profile JSON file — an OS path, not resolved under the data dir. An invalid profile fails startup
--returnstringunset (instance / profile default, else standard)return preset (full, standard, minimal) for a tool request without its own return block. An unknown preset fails startup (PULSE_RETURN_INVALID)
--limitstring, repeatableunset (profile limits, else the built-in defaults)Instance resource limit as name=value (e.g. max_groups=1000000, request_timeout=30s). An unknown name or bad value fails startup (CLI_INPUT)
--metrics-addrstringunset (no port opened)Serve Prometheus text-format metrics on GET /metrics at host:port (:0 picks a free port). A bad or busy address fails startup (CLI_INPUT)
--log-level / --log-formatstringoff / textRoot persistent flags (Global flags): structured logs on stderr

--data-dir is required in one of its two forms (env var or flag). The MCP server fails to start otherwise:

data directory required: set PULSE_DATA_DIR or pass --data-dir

–feature-profile

Loads a feature profile into the served instance. The value is a host OS path — absolute or relative to the process working directory, never resolved under --data-dir — read and strictly parsed before pulse.New. The flag wins over PULSE_FEATURE_PROFILE; the env var is read by this leaf only, so every other pulse command stays unprofiled. Any invalid profile (unreadable file, malformed JSON, an unknown key or feature, an unmet dependency) aborts startup with a PULSE_FEATURE_PROFILE_* error. The server then registers only the tools and prompts the profile offers; a hidden tool fails like a nonexistent one. A profile whose behaviour sets disable_cohort_scan, or that omits mcp_extra:cohort_resources, skips the startup scan as if --no-cohort-scan were passed.

The stderr startup notice reports the EFFECTIVE settings, so a profile that turned the scan off reads cohort-scan: false even without the flag, and a loaded profile is named by its profile label ((unnamed) when it has none):

pulse mcp: serving over stdio (data dir: /mnt/cohorts, bind-on-open: true, cohort-scan: false, return: standard, feature-profile: self-serve)

Embedders get the same effective view from mcpserve.Describe(p, opts), which returns a mcpserve.ServeInfo (CohortScan, FeatureProfileLoaded, FeatureProfile, DefaultReturn, Limits). The library contract is Feature Profiles.

–return

MCP agents pay tokens for every key they receive, so the request-carrying tools (pulse_process, pulse_predict, every pulse_compose slot, every pulse_process_chain stage) shape a request WITHOUT its own return block by the standard preset: the primary result plus metadata, tests, crosstab and overlays, never components. The library default stays full. Precedence, highest first:

  1. the request’s own return block (a slot’s, a stage’s);
  2. --return / gosdk.Config.DefaultReturn / mcpserve.Options.DefaultReturn;
  3. the instance default — pulse.Options.DefaultReturn, else the feature profile’s return;
  4. the built-in standard.

--return full restores the unshaped (pre-default) output byte for byte. The startup notice always names the effective preset (return: standard; (custom) when the instance default is a preset-less include/exclude selection), and ServeInfo.DefaultReturn carries the same value. An engine that disables components keeps them off under any return; only a request disable_components: false re-opens them. The full contract is Response shaping.

–limit

Sets the served instance’s resource limits — the pulse.Options.Limits layer — one name=value per flag, repeatable:

pulse mcp --limit max_groups=1000000 --limit request_timeout=30s

Names are the snake_case limit keys the feature profile limits section and every PULSE_LIMIT_* error’s limit detail use: request_timeout, max_groups, max_crosstab_cells, max_estimated_memory, max_matrix_dim, max_compose_slots, max_chain_stages, max_join_build_rows, max_derived_columns. A value is a positive integer (underscores allowed, 10_000_000), a Go duration for request_timeout (30s, 2m), unlimited (or -1) for no limit, or 0 for the default. Per key, --limit wins over the feature profile’s limits, which wins over the built-in default; a later flag for the same name wins. A malformed pair, an unknown name or a bad value aborts startup with CLI_INPUT (details {flag, value, limit?}).

The startup notice echoes the limits that differ from the defaults, in the same grammar:

pulse mcp: serving over stdio (data dir: /mnt/cohorts, bind-on-open: true, cohort-scan: true, return: standard, limits: request_timeout=30s max_groups=1000000)

Agents read the effective values from the manifest’s limits block (cache key (pulse_version, feature_set_digest, limits_digest)); embedders read the same block from ServeInfo.Limits.

–metrics-addr

Opt-in Prometheus exporter. Unset (the default), pulse mcp opens no port and records no metrics. Set, it binds host:port after the tool surface registers — a bad or busy address fails startup with CLI_INPUT — and serves the Prometheus text format on GET /metrics (every other path is 404) until the server exits. The startup notice names the bound address, so :0 reveals the port it picked:

pulse mcp --log-level info --log-format json --metrics-addr 127.0.0.1:9090
# Stderr: pulse mcp: serving over stdio (data dir: /mnt/cohorts, bind-on-open: true, cohort-scan: true, return: standard, log-level: info, metrics: 127.0.0.1:9090)

Bind a loopback or otherwise private address: the endpoint carries no authentication. Metric labels are operation kinds, scopes, phases, error codes, limit names and hook names — never cohort paths or data.

–bind-on-open

When a session calls pulse_inspect successfully, the server can register session-scoped tool variants whose JSON Schemas constrain field-name parameters to the cohort’s actual fields. This narrows the LLM’s choices and prevents typos at parameter-binding time.

Default: true. Pass --bind-on-open=false if your client binds tool schemas itself.

The binding logic lives in the SDK-free core internal/mcp/bind.go (the per-session server mutation that consumes it lives in the go-sdk adapter, mcp/gosdk/bind.go); see Adding an MCP tool for the LLM-facing implications.

–no-cohort-scan

At startup the server walks the data directory once and registers every .pulse file it finds as an exact-match pulse://<path> resource, so resources/list enumerates the available cohorts. On a large, remote or volatile data root that walk is the most expensive thing startup does, and the enumeration is not always worth it — a client that already knows which cohort it wants never reads the list.

--no-cohort-scan (or PULSE_MCP_NO_COHORT_SCAN=1) skips the walk. The pulse://{+path} resource template is registered either way, so every cohort stays readable by URI:

resources/read  pulse://surveys/2026.pulse   # works with or without the scan
resources/list                               # lists cohorts only WITH the scan

Nothing else changes: the static pulse://schema resource, the pulse-skill:// resources, every tool, and pulse_inspect (the tool route to the same header+schema JSON) are unaffected.

Embedders reach the same knob as gosdk.Config{DisableCohortScan: true} or mcpserve.Options{DisableCohortScan: true}; both default to scanning.

Wiring it into Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "pulse": {
      "command": "/usr/local/bin/pulse",
      "args": ["mcp"],
      "env": {
        "PULSE_DATA_DIR": "/var/data/pulse"
      }
    }
  }
}

Restart the client. The Pulse tools (pulse_manifest, pulse_inspect, pulse_predict, pulse_process, pulse_compose, pulse_sample, pulse_facet, pulse_import, pulse_dedup, pulse_materialize, pulse_recommend, pulse_explain, pulse_drop, pulse_imports_list, pulse_examples_search, pulse_examples_get, pulse_errors_lookup, pulse_skills_list, pulse_skills_get) and resources (pulse://*.pulse, pulse-skill://*) appear in the tool/resource list. The per-cohort pulse://*.pulse entries come from the startup scan — see --no-cohort-scan.

Wiring it into Claude Code

~/.claude.json (or per-project .claude.json):

{
  "mcpServers": {
    "pulse": {
      "command": "/usr/local/bin/pulse",
      "args":    ["mcp"],
      "env":     { "PULSE_DATA_DIR": "/var/data/pulse" }
    }
  }
}

The full LLM-side recipe (including resource URIs and the schema binding details) is in Adding an MCP tool and Wiring an MCP client.

Exit codes

pulse mcp is a long-running process. It exits non-zero only on fatal startup failure (missing data dir, transport error). Once serving, an MCP client controls the lifecycle.

Examples

Foreground run for debugging

PULSE_DATA_DIR=/tmp/pulse-data ./bin/pulse mcp
# Stderr: pulse mcp: serving over stdio (data dir: /tmp/pulse-data, bind-on-open: true, cohort-scan: true, return: standard)

Disable schema binding

PULSE_DATA_DIR=/tmp/pulse-data ./bin/pulse mcp --bind-on-open=false

Skip the cohort scan on a large data root

PULSE_DATA_DIR=/mnt/cohorts ./bin/pulse mcp --no-cohort-scan
# Stderr: pulse mcp: serving over stdio (data dir: /mnt/cohorts, bind-on-open: true, cohort-scan: false, return: standard)

Or from an MCP client config, which sets env rather than args:

"env": { "PULSE_DATA_DIR": "/mnt/cohorts", "PULSE_MCP_NO_COHORT_SCAN": "1" }

Inspect what the server registers

# Manifest exposes the MCP tool list
pulse --json | jq '.data.mcp_tools[]'