Skip to main content

MCP Server

The local MCP server, authorization tiers, time-bounded grants, the audit log, and how to connect external clients.

Updated
Reviewed

What the MCP server is

Daintree runs a local Model Context Protocol server, and it inverts the usual relationship. Most MCP servers are tools your agent connects to. Here Daintree is the server. External clients like Claude Code, Cursor, and Cline connect to it on 127.0.0.1 and drive the running app: listing worktrees, polling terminal output, reading Project Pulse, staging files, opening pull requests. Daintree's own in-app help agents connect to the same server through internal sessions.

The server is loopback-only, opt-in, and disabled by default. The default port is 45454. v0.12 added time-bounded per-tool grants, a ring-buffer audit log with anomaly detection, token-bucket rate limiting, and turn-outcome diagnostics. The transport implements the Model Context Protocol Streamable HTTP spec, with a legacy SSE endpoint kept for clients still on the older revision. For transport details, see the MCP 2025-03-26 spec.

Enable the server

Open Settings (Cmd+, on macOS, Ctrl+, on Windows and Linux) and switch to the MCP Server tab. Flip the master toggle. The server starts immediately and shows one of four runtime states next to the toggle: disabled, starting, ready, or failed.

The default port is 45454. If that port is taken, Daintree tries the next ten ports in sequence, 45455 through 45464. When the bound port differs from the configured one, a warning line appears below the port field, and any external client still pointed at the old port will fail to connect. Re-copy the config snippet to pick up the bound port.

Connect a client

The MCP Server tab has a Copy MCP config button. It copies a ready-to-paste JSON snippet to your clipboard:

{
  "mcpServers": {
    "daintree": {
      "type": "http",
      "url": "http://127.0.0.1:45454/mcp",
      "headers": { "Authorization": "Bearer daintree_<your-key>" }
    }
  }
}

Paste it into your client's MCP config file. For Claude Code, that's .mcp.json at the project root, or ~/.claude.json globally. For Cursor and Cline, paste it into the client's own MCP settings UI. Any client that speaks Streamable HTTP or the legacy SSE transport can connect.

Tip
Claude Code can install the snippet from the command line:
claude mcp add --transport http daintree http://127.0.0.1:45454/mcp \
  --header "Authorization: Bearer daintree_<your-key>"
This wires the headers correctly. A few clients still get that wrong when you paste the JSON by hand.
Note

Streamable HTTP at /mcp is the primary transport (MCP spec 2025-03-26). The legacy SSE endpoint at /sse stays online for clients that haven't migrated. Use "type": "http" for modern clients. Use "type": "sse" only if your client doesn't support Streamable HTTP yet.

API key

Daintree generates an API key on the first server start, in the format daintree_<32-hex>. The key is stored in the app's electron-store and survives restarts. The server validates incoming bearer tokens with a timing-safe SHA-256 comparison. A missing or wrong token returns 401 Unauthorized with WWW-Authenticate: Bearer realm="Daintree MCP".

To rotate the key, click Rotate API key on the MCP Server tab. The confirmation dialog asks you to type the last four characters of the current key. Rotation invalidates every external client holding the previous key at once. After rotating, re-copy the config snippet and paste it into each client.

The server also enforces loopback by inspecting the Host header. Anything other than 127.0.0.1:<port> or localhost:<port> returns 403 Forbidden. That's a second check on top of the OS-level loopback bind.

Authorization tiers

Every session resolves to one of five authorization levels. External clients that authenticate with the API key get the external tier and the full curated tool allowlist. Daintree's in-app help agents start at a per-project tier you choose in Project Settings > General > Agent integrations.

TierWho gets itWhat it can do
offDefault for new projectsNo MCP access for in-app agents. External API key clients are unaffected.
workbenchRead-only in-app agentsWorktree status, terminal output, file search, project history.
actionPer-project opt-inWorkbench + create worktrees, inject context, stage changes.
systemPer-project opt-inAction + commit, push, delete worktrees, open issues and pull requests.
externalAPI key clientsFull curated tool allowlist (~80 tools). Sits above system.

The Project Settings choicebox shows the same four options listed above. A tier switch takes effect on the next agent call. No app restart, no session reset.

Tier mismatch banner

When an in-app agent calls a tool above its current tier, Daintree denies the call and shows a banner in the Help panel naming the tool. The banner offers three actions:

  • Approve once mints a 15-minute sliding-TTL grant keyed on (sessionId, toolId). Each successful dispatch through the grant refreshes the window. The grant is pinned to the window that minted the session.
  • Always allow for this project raises the session's tier in memory to the lowest tier that permits the tool. The elevation has a hard 30-minute awake-time decay. Sleep and wake do not extend it. Once it decays, the next out-of-baseline call brings the banner back.
  • Cancel denies the call and writes an audit record.

If the same (sessionId, toolId) pair is denied twice in a row, the banner is suppressed for the rest of the session. Daintree still writes an audit record for every denial, with bannerSuppressed: true set. The counter resets when a grant is issued or the session ends. Grant lifecycle events (grant.issued, grant.expired, grant.revoked) interleave with tool-call records in the audit log.

Confirmation modal

Tools annotated with danger: "confirm" route through a native confirmation dialog before they run. The dialog shows the action title in the format Run '<actionTitle>'?, the action description, and a redacted summary of the call arguments. Long strings collapse to <string: N chars> and nested objects to <object>, so secrets don't leak into the prompt.

The renderer auto-cancels after 28 seconds. Concurrent confirm calls queue FIFO behind the visible modal. While the modal is open, the audit record sits at confirmation-pending, then resolves to approved, rejected, or timeout.

Note

The 28-second renderer timeout is 2 seconds shorter than the 30-second main-process dispatch deadline. The renderer cancels first, so the audit log records a clean timeout rather than a torn-down dispatch.

The modal only fires for clients that advertise the MCP elicitation.form capability. Clients without it get an automatic rejection, and the tool does not run. Action handlers can also gate server-side on _meta.confirmed: true as a second check.

Tools by tier

Tools are grouped by what they read or change. Each tier inherits the tools below it. The lists below cover the canonical sets. External API key clients see the full curated allowlist of around 80 tools, which includes a few introspection entries beyond system.

Workbench (read-only)

  • Actions metadata. actions.list, actions.search, actions.getContext.
  • Project info. project.getAll, project.getCurrent, project.getStatus, project.getRecent.
  • Worktrees. worktree.list, worktree.getCurrent, worktree.listBranches, worktree.getStatus.
  • Files. files.search, file.view.
  • Terminals. terminal.list, terminal.getOutput, terminal.getStatus.
  • Agent state. agent.getState.
  • Git read-only. git.getProjectPulse, git.getFileDiff, git.listCommits, git.getStagingStatus, git.snapshotGet, git.snapshotList.
  • GitHub read-only. github.listIssues, github.listPullRequests, github.getIssueByNumber, github.checkCli, github.getRepoStats.

Action (Workbench plus non-destructive writes)

  • Worktree creation. worktree.createWithRecipe, worktree.setActive, worktree.refresh.
  • Terminal lifecycle. terminal.new, terminal.inject, terminal.sendCommand, terminal.close, terminal.kill, terminal.waitUntilIdle.
  • Recipes. recipe.list, recipe.run.
  • Agents. agent.launch, agent.terminal, agent.focusActive, agent.focusByIndex.
  • Workflows. workflow.startWorkOnIssue.
  • App and project settings. app.theme.set, app.theme.list, project.update, project.saveSettings, project.muteNotifications.
  • Editor. file.openInEditor.
  • CopyTree. copyTree.injectToTerminal.

System (Action plus destructive)

  • Destructive worktree. worktree.delete.
  • Git writes. git.commit, git.push, git.stageFile, git.unstageFile, git.stageAll, git.unstageAll, git.snapshotRevert, git.snapshotDelete.
  • Forge. forge.openIssue, forge.openIssues, forge.openPRs, forge.openCommits, forge.assignIssue, forge.validateToken.
  • GitHub writes. github.openPR.
  • CopyTree writes. copyTree.generateAndCopyFile.

Progressive tool disclosure

Action manifest entries can declare a visibility hint. Tools with mcpVisibility: "hidden" are dropped from tools/list and rejected at dispatch with TIER_NOT_PERMITTED. Tools with mcpVisibility: "discoverable" are dropped from tools/list but stay findable through actions.list and actions.search. This keeps the default surface small while letting an agent introspect for advanced tools when it needs them.

Resources

Daintree exposes four MCP resources. Two of them support live subscriptions. The URI scheme is daintree://<host>/<id>/<verb>. Payloads larger than 50 KB are truncated with a [truncated] marker. Subscriptions emit notifications/resources/updated with no payload, so the client re-reads the resource on each notification.

URIBacking dataSubscribable
daintree://worktree/{id}/pulsegit.getProjectPulse (60 days)Yes
daintree://terminal/{id}/scrollbackterminal.getOutput (last 200 lines, ANSI stripped)No
daintree://agent/{id}/stateAgent availability storeYes
daintree://project/current/issuesgithub.listIssuesNo

Access to each resource is gated by the backing action's tier. An agent on workbench can read the pulse and scrollback resources, but not anything that resolves to a write tool.

Slash command prompts

The server registers three MCP prompts. Most clients surface prompts as slash commands in their input autocomplete: /start_issue, /triage_failed_agent, /triage_terminals.

PromptArgumentsWhat it does
start_issueissue_number (required)Renders a structured prompt to start work on a GitHub issue, with the active worktree and recipe context filled in.
triage_failed_agentterminal_id (optional)Renders a triage prompt for a stuck or failed agent. Pass a terminal ID and the prompt embeds up to 100 lines of recent output, capped at 16 KB.
triage_terminalsNoneA static polling recipe. It explains how to batch terminal.getStatus calls, when to set includeOutput, and how to pace the next round with ScheduleWakeup.

Idempotency dedup

Eight creation tools have caller-safe retries built in: terminal.new, worktree.createWithRecipe, agent.launch, recipe.run, git.commit, git.push, forge.openIssue, github.openPR. The server keeps a per-session deduplication store with a 120-second TTL and a 256-entry FIFO cap.

A caller can pass an explicit requestKey (up to 256 characters) as a sibling of _meta in the call arguments. The server keys the entry as <actionId>:rk:<requestKey>. Without a requestKey, the server hashes the action ID and the argument object into <actionId>:auto:<SHA256>.

An in-flight duplicate (same key, same args, mid-call) joins the original promise. A post-completion duplicate within the 120-second window returns the cached result. The audit log records the outcome as dedup. A retry with the same requestKey but mismatched args is rejected with MCP_DEDUP_KEY_COLLISION_CODE, and the audit outcome is collision.

Rate limiting

Each (sessionId, toolId) pair gets a token bucket. The rate-limit check runs after tier and grant authorization but before idempotency dedup. That ordering means an unauthorized call never spends a token, and a tight loop reusing one dedup key is still bounded. Rejections return the MCP_RATE_LIMITED error code with a retryAfter hint in seconds. The audit outcome is rate_limited.

BucketLimitTools
highFreqRead60 / minuteterminal.getOutput, terminal.getStatus, actions.getContext
standard30 / minuteThe default for any tool not mapped to another bucket.
mutation10 / minutegit.commit, git.push, forge.openIssue, github.openPR

Daintree also has an optional abuse policy, off by default. Turn it on in Settings and the server tracks 401s and tier-mismatch denials in a 60-second sliding window. Five denials trips the cap: the session is revoked and the renderer receives a MCP_SESSION_REVOKED event. Reconnecting mints a fresh session.

Audit log

Every dispatch writes a record to a ring buffer (default 500 entries, configurable between 50 and 10,000 on the MCP Server tab). Records carry id, timestamp, toolId, sessionId, tier, a redacted argsSummary, result, severity, and optional fields for errorCode, durationMs, confirmationDecision, tierHint, bannerSuppressed, turnId, and repeatCount. Grant lifecycle events (grant.issued, grant.expired, grant.revoked) share the same ring buffer and carry a discriminator type field.

Filters narrow the view by time range (5 minutes, 1 hour, 24 hours, or all) and by result. The viewer also renders a per-tool latency table, split into success and failure columns, with p50 and p95 latencies tagged with SLO bands:

BandRange
Instant< 200 ms
Fast< 1000 ms
Standard≤ 5000 ms
Slow> 5000 ms

Once the buffer holds at least 50 records, Daintree runs four anomaly detectors against new dispatches:

  • Latency drift. A per-tool modified z-score using the median absolute deviation. A z-score of 3 or higher flags the record.
  • Failure cluster. Three or more failures inside any 10-record sliding window.
  • p95 z-score. A tool's p95 latency measured against the median p95 across all tools, with a minimum of five tools.
  • First-seen combination. The first time a given (toolId, tier) pair appears.

Export the audit log as newline-delimited JSON (NDJSON), or copy it as pretty-printed JSON. Clear wipes the buffer behind a confirmation dialog.

Turn outcome diagnostics

When an in-app assistant moves from active to passive (idle, waiting, completed, or exited), Daintree classifies the turn against a priority waterfall of 11 outcomes. Every tool call inside the turn carries the same turnId, so the audit log can be filtered or aggregated per turn. The Turn outcomes section on the MCP Server tab shows per-tool rollups with counts for each outcome class.

  • Answered. The turn produced a meaningful answer.
  • Hedged. The model hedged ("I'm not sure", "I don't know").
  • Refused. The model refused on policy grounds.
  • No docs found. A docs lookup returned no results.
  • Tier rejected. The final tool call was denied by tier or grant.
  • MCP not ready. The MCP server wasn't ready when the turn started.
  • Agent stuck. The watchdog timed out while the agent waited for input.
  • Tool error. The final tool call returned an error.
  • Resume stale. Session resume failed because no conversations were available.
  • Reasoning loop. The same (toolId, argsSummary) repeated three or more times inside the turn.
  • Unknown. The output was too short to classify.

Runtime state and supervised restart

The server has four runtime states: disabled (toggle off), starting (enabled, not yet listening), ready (bound and accepting), and failed (the most recent start attempt failed, with the error shown next to the toggle). The state badge sits beside the master toggle on the MCP Server tab.

If the HTTP server closes unexpectedly, a supervisor restarts it with exponential backoff: a 500 ms base delay, a 2× multiplier, capped at 15 seconds, with ±250 ms of jitter on each attempt. Thirty seconds of stable uptime resets the attempt counter. After five failures in a row, the supervisor parks the server in failed and shows the last error.

Sessions that go 30 minutes without activity are reaped. Reaping a session revokes its grants with reason session-ended. The next call from that client re-authenticates and mints a fresh session.

Troubleshooting

  • 401 with the right config. Some clients drop or misplace the Authorization header on Streamable HTTP. Re-copy the snippet, or use the Claude Code CLI shortcut, which wires the headers correctly. Check that the key hasn't been rotated since you pasted it.
  • Wrong transport. "type": "http" points at /mcp. "type": "sse" points at /sse. Mismatch them and you get a hang or a 404.
  • Port conflicts. If 45454 was busy when the server started, Daintree bound to the next free port up to 45464. Re-copy the config to pick up the new URL. A stale URL returns a connection refused or a 404.
  • Key rotation surprise. Rotating the API key invalidates every external client holding the previous key at once. Re-paste the snippet into each client.
  • Idle session expiry. A client that sits idle for 30 minutes loses its session and has to reconnect. For most clients, the reconnect happens automatically on the next call.
  • Confirm tool from a client without elicitation. Tools annotated danger: "confirm" auto-reject when the client doesn't advertise elicitation.form. Use a client that supports elicitation (Claude Code does), or trigger the tool from inside Daintree, where the modal is always available.
  • Loopback rejected. The server requires the Host header to be 127.0.0.1:<port> or localhost:<port>. Some proxies and container setups inject a different host. Disable the proxy, or set the host header explicitly.
  • Settings covers the MCP Server tab in the wider settings context.
  • Projects documents the per-project General tab where the tier picker lives.
  • AI Agents covers the CLI agents that run inside Daintree terminals and the built-in Help Agent.
  • Code Forge covers token setup for the github.* and forge.* tools.
  • Recipes covers the recipe.run tool surface.
  • Worktrees covers worktree.createWithRecipe and the project tier as it applies to worktree creation.