---
name: cse-mcp-setup
description: Configure or repair local cse-tools MCP access for an already-enrolled operator machine.
---

# CSE MCP Setup

## Compact MCP routing

Follow the shared [compact MCP routing contract](../../shared/compact-mcp-routing.md). Interactive clients default to `/mcp/full`, which exposes named operations plus `cse_capabilities`, `cse_read`, `cse_apply`, `context_assemble`, and `cse_session_info`; `/mcp` remains the five-facade compatibility plane. Direct named mutations and facade-routed mutations both remain two-phase: dry-run first, then repeat the mutation with `execute:true`, justification, and the returned `preview_digest`. When using `cse_apply`, follow its outer/inner execution contract from the shared guidance.

Use this skill when the user asks to set up or repair **local cse-tools MCP tools** (Factory / Codex / Claude / plugin-cache installs). Assumes the operator machine is already enrolled with `okta-aio` (Okta enrollment is a one-time step done outside this repo).

## Scope

- Runs in an interactive local terminal on an already-enrolled machine (`okta-aio auth status --json` returns healthy).
- Inspects runtime configs, proposes a diff, and asks before applying.
- Never runs first-time headless Okta enrollment.
- Does not install Salesforce CLI — Salesforce data flows through local `cse-tools` MCP `get_salesforce_account`, which reads the Kepler DB mirror.
- Does not check or request `LINEAR_API_KEY`; non-Linear skills do not need Linear auth.

## Ten-Phase Flow

Run each phase in order. Stop at the first blocker, fix it, then resume. Every phase is safe to re-enter — all writes are diff-gated through phase 5.

### Phase 1 — Detect repo + runtime configs

Resolve the git root once (`git rev-parse --show-toplevel`) and locate every config that may need patching:

- Factory repo-root: `.factory/mcp.json`
- Codex + Claude plugin install (source): `plugins/cse-tools/.mcp.json` (resolved via `${PLUGIN_ROOT}` / `${CLAUDE_PLUGIN_ROOT}` after `/plugin install cse-tools@postman-cse`)
- Factory plugin install (source): `plugins/cse-tools/mcp.json` (resolved via `${DROID_PLUGIN_ROOT}`)
- Claude personal overrides (gitignored): `.claude/settings.local.json`
- Factory plugin registry: `~/.factory/plugins/installed_plugins.json` (informational — used to locate `${DROID_PLUGIN_ROOT}`)

Record which configs exist; missing configs are fine for runtimes the operator doesn't use.

### Phase 2 — Prerequisite checks (no secret output)

Verify each of the following and report status. Do not print credential values.

- `node --version` — require ≥ 20.
- `jq --version` — required for the shell preflight.
- `okta-aio auth status --json` (or `npx -y --package=@postman-cse/okta-aio@0.6.1 okta-aio-js auth status --json`) — expect `{"ok": true}` for `okta` + every platform the operator uses.
- `source "${PLUGIN_ROOT:-${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$PWD/plugins/cse-tools}}}/.agents/shared/skill-bootstrap.sh"` — resolves `CSE_TOOLD_BIN`. The daemon mints/refreshes keychain entries on its own; this skill never forces a mint. Do not print or require shell auth env vars.

For live tool probes in this skill, keep call shapes strict: Slack direct search uses `q`, Jira checks should request explicit `fields` with bounded `maxResults`, and an empty first-pass Granola read should trigger one widen/retry pass before you declare the surface unavailable.

If anything fails, pause and surface the gap. Do NOT apply config changes against a wedged operator.

### Phase 3 — Auth readiness

This is a repair skill, so check readiness before minting. Source the bootstrap, then read daemon auth status; only force a mint if it is not ready:

```bash
source "${PLUGIN_ROOT:-${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$PWD/plugins/cse-tools}}}/.agents/shared/skill-bootstrap.sh"
```

Call `cse_session_info` via the local `cse-tools` MCP. If `readiness.ready` is true, the daemon already holds fresh credentials. If it is false, request `cse_session_info({"force_refresh": true})` once; if the daemon is unreachable or `auth_maintenance.state` is `blocked`, surface the named `operator_action` (sign into Granola, refresh Postman sign-in) and stop. Do not add auth env vars to runtime configs.

### Phase 4 — Build proposed patch

For every runtime found in phase 1, construct the exact JSON/TOML patch. Keep these invariants:

- Local `cse-tools` MCP is HTTP, served by the resident `cse-toold serve` daemon on `127.0.0.1:9901`. Every interactive runtime's `cse-tools` entry is `{"type":"http","url":"http://127.0.0.1:9901/mcp/full"}` (Factory keeps `"disabled": false`). There is no per-runtime command, no `cse-toold-exec` MCP wrapper, and no plugin-root path in the entry; one daemon serves every session, so the same JSON works across Claude, Codex, and Factory.
- Slack reads/searches stay inside the local `cse-tools` MCP through daemon-owned xoxe bearer clients. Do not add a remote Slack MCP server entry.
- Kepler full tools are aggregated inside the signed local `cse-tools` MCP process; do not create a separate env-header MCP entry.
- Granola uses the local `cse-tools` MCP; no dedicated MCP server entry.

Show the unified diff before proceeding.

### Phase 5 — Apply patch (gated)

Ask the user to confirm the diff. On confirm, write each config file atomically (temp file + rename), preserving existing unrelated keys.

### Phase 6 — Restart / reconnect

Tell the user which runtimes need what:

- Local `cse-tools` MCP is the resident daemon; it refreshes auth internally and needs no restart. It must be up before a client dials `127.0.0.1:9901` (no auto-start-on-connect). If the health probe fails, bootstrap or kickstart the LaunchAgent (`launchctl kickstart -k gui/$UID/com.postman.cse-toold`).
- Slack reads use the daemon proxy and Kepler tools use local `cse-tools` MCP; reconnect the runtime only if the MCP process itself was started before the config change.
- Factory plugin installs should launch the packaged signed daemon for `cse-tools`. The plugin cache is not the right place for auth keys.

### Phase 7 — Protocol smoke

The MCP plane is served by the already-running daemon, so a `tools/list` does not spawn a new Keychain-backed process. Probe `/mcp/full` through the active runtime and assert that it contains the five facades (alongside the named tools):

```bash
response="$(curl -fsS -X POST http://127.0.0.1:9901/mcp/full \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}')"
jq -e '
  (["context_assemble", "cse_apply", "cse_capabilities", "cse_read", "cse_session_info"] -
   [.result.tools[].name]) == []
' <<<"$response" >/dev/null
```

Treat a failed assertion as a blocker. If the plane refuses the connection,
bootstrap or kickstart the LaunchAgent first. Facade-only compatibility clients may probe `/mcp` separately and expect exactly five tools.

### Phase 8 — Surface-scoped smoke (live, optional)

With auth readiness confirmed, run one facade-routed read per wired surface. Skip any surface the operator has not enabled.

- Jira: `cse_read({ "capability": "jira_search", "arguments": { "jql": "project = CSE ORDER BY updated DESC", "fields": ["summary","status"], "maxResults": 1 } })`
- Slack read: `cse_read({ "capability": "slack_search", "arguments": { "q": "<known distinctive phrase>" } })`
- Slack write: preview opening a DM to the operator's own user id with `cse_apply({ "capability": "slack_open_dm", "arguments": { "users": "<operator-user-id>" }, "execute": true, "justification": "Preview the operator self-DM channel" })`. Then apply with `cse_apply({ "capability": "slack_open_dm", "arguments": { "users": "<operator-user-id>", "execute": true, "preview_digest": "<digest-from-preview>" }, "execute": true, "justification": "Verify the operator self-DM channel" })` and verify that the result contains the DM channel id.
- Granola (optional): route `granola_get_account_info` and a bounded `granola_list_meetings` through `cse_read`. If the active workspace is wrong or empty, report that scope before trying the REST `granola_list_notes` cross-workspace fallback.
- Google Docs: call `cse_session_info` directly, then route the read capabilities `google_file_text` and `google_docs_replace_text_preview` through `cse_read` against a disposable/test doc. Do not smoke live apply except against a disposable doc using the two-phase `cse_apply` flow.
- Kepler (primary path for Salesforce + communications): route `list_customers` through `cse_read` with a known customer name, then route `get_salesforce_account` through `cse_read` with the returned `crm_id`. The response must include non-null `team_id` for any account with a configured Postman_Team__c record.

### Phase 9 — Interpret failures

Map each failure to a next step:

- `okta-aio auth status` unhealthy → surface the operator re-enrollment action (one-time step, done outside this repo). This skill won't mint the Okta broker envelope.
- 401 on Jira/Slack/Granola → request `cse_session_info({"force_refresh": true})`, retry once, then stop and report the exact surface still failing. A 403 is blocked/revoked: surface the operator action without retry. Do not switch to raw auth helpers or env-header fallbacks.
- `127.0.0.1:9901/mcp` refuses the connection or the daemon is missing → the resident `cse-toold serve` daemon is not running; bootstrap or kickstart the LaunchAgent, or reinstall the plugin / run `npm install` in `plugins/cse-tools` (the lockfile is gitignored so this resolves the current published binary) to restore the signed binary, then re-probe.
- `get_salesforce_account` returns `team_id: null` with a non-empty `teams[]` → the account has no `Postman_Team_ID__c` on any Postman_Team__c record; confirm with the customer which team is in scope.
- Kepler tools return 401 → request `cse_session_info({"force_refresh": true})`, retry once, then check okta-aio enrollment/keychain health. A 403 is blocked/revoked and must surface its operator action. Do not add Kepler API-key env headers.
- `get_salesforce_account` returns `403 legacy_key_scope_required` → the okta-worker Kepler issuer minted a key without `kepler:accounts:read`. Report it as a scope issuance defect; never fall back to direct Salesforce access.

### Phase 10 — Report outcome

Produce a final status block with, per runtime: config path patched (or "skipped — runtime not in use"), protocol smoke (ok/fail), per-surface smoke status, and any remaining blockers with their exact next step. Do not print credential values.

## Safety rules

- Never print keychain credentials, OAuth tokens, browser cookies, API keys, or any raw MCP bearer.
- Missing Granola is degraded success, not failure.
- Do not expose or recommend `slack.raw`; Slack reads and writes go through local `slack_*` MCP tools only.
- Never patch a runtime config that already has a healthy `cse-tools` entry pointing at the compact URL — re-patching is idempotent but should be surfaced as "no change required" to keep the diff honest.
