Skip to content
Conduit

mcp server

silolink

MCP server bridging Claude Code sessions on remote machines to Portablemind conversations. Public source mirror of @dsiloed/silo-link.

portablemind-ai/silolinkcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "silolink": {
      "command": "npx",
      "args": [
        "-y",
        "@dsiloed/silo-link"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 25, 2026

Public source mirror. This is the read-only source mirror for the @dsiloed/silo-link npm package. Issues and questions are welcome here.

Portablemind is the platform SiloLink connects to — https://app.portablemind.ai. DSiloed is the name of its backend service, which is why it appears throughout this README and in the npm package name.

SiloLink — Claude Code Remote Bridge

SiloLink is a local daemon that connects Claude Code sessions to DSiloed conversations. It provides an MCP server that Claude Code connects to, and maintains a real-time WebSocket connection to DSiloed via ActionCable. Messages flow bidirectionally — you can launch, monitor, interact with, and command Claude Code sessions from the DSiloed web UI, Slack, SMS, or Discord.

See it in action — Interactive Demo | Documentation

                               ┌──────────────────────┐
                               │     DSiloed Server    │
                               │                      │
Claude Code (tmux) ──MCP──►    │                      │
Claude Code (tmux) ──MCP──► SiloLink ──WebSocket──►  ActionCable
Claude Code (tmux) ──MCP──►  :3579    ──REST API──►  ├── ConversationChannel
                               │                      ├── ControlChannel
                               │  Claude Launcher     │
                               │  Message Queue       │  Web UI / Slack
                               │  Session Manager     │  Discord / SMS
                               └──────────────────────┘

Features

  • Launch Claude sessions from the web UI — click "+" in the SiloLinks section to spawn a new session
  • Auto-launch — send a message to any SiloLink conversation and Claude starts automatically
  • Multi-session — each conversation gets its own Claude Code tmux session
  • Message buffering — messages that arrive before Claude registers are queued and delivered
  • Status notifications — "Starting session..." and "Session ready!" feedback in the conversation
  • Agent Dashboard — active sessions appear in Mission Control with real-time status
  • Control Channel — launch/stop/status commands via ActionCable from the UI
  • Channel linking — command Claude from Slack, Discord, Teams, or SMS
  • Session continuity — remote_load_context restores prior conversation history on session restart
  • Workspace isolation — git worktrees give each session its own branch and working directory
  • File claim tracking — advisory soft-locks with cross-session conflict notifications
  • Provider abstraction — AgentLauncher interface supports Claude, Gemini, and Codex (OpenAI) adapters

Installation

npm install -g @dsiloed/silo-link

This installs the silolink CLI globally.

Configuration

silolink config

The wizard first asks whether the daemon is running for a user or an agent and then collects only what's needed. Your answer is persisted as identity_type in the config and changes how the spawned Claude Code session is launched (see below).

  • User (default): prompts for host, tenant, login (email or username) and password. SiloLink calls POST /api/v1/users/login, captures the returned JWT, and stores it in ~/.silolink/config.json (mode 0600). Your password is never written to disk — only the JWT.
  • Agent: prompts for host, tenant, agent name (the sub claim), and the JWT to paste in. Generate the token from the LLM Manager UI: open the agent's edit dialog, expand SiloLink Authentication, and click Generate SiloLink Token. The agent path never needs Sorcery credentials, since agents don't have a human login.

The token is long-lived. If the server ever rejects it (password change, tenant key rotation, agent rename), re-run silolink config to refresh it.

How identity_type changes session launches

The choice you make in the wizard controls where the spawned Claude Code session starts (which is the same dir CC reads .mcp.json from):

  • identity_type: "user" — single-env remote-bridge use case. CC launches in projects_path (or in repo_path if a launch command supplies one). CC inherits any CLAUDE.md from your projects dir, and you typically run silolink setup-claude-md once to inject the SiloLink protocol section into it. There's only one writer of the .mcp.json in projects_path, so collisions are not a concern.

  • identity_type: "agent" — multi-agent SDLC orchestration where several silolink envs (e.g. lead-tester, vixen) run on the same laptop. Each env gets its own per-env staging dir at ~/.silolink/staging/<env>/, and CC launches there so its .mcp.json (which includes the env-specific JWT) is never shared with a sibling env. A bare repo_path in a launch command is treated as informational; the agent's runbook tells the spawned CC where to cd for repo work. When a launch supplies both repo_path and branch, silolink creates a per-ticket worktree under ~/.silolink/worktrees/<repo>/<branch>/ — also identity-isolated.

Worktree-mode (repo_path + branch) is identity-safe in either mode and works the same way for both.

If you ever migrate an env from user to agent (e.g. a personal config that's grown into an agent setup), re-run silolink config --env <name> and answer agent — the daemon will pick up the new identity_type on next restart.

Non-interactive setup

For automated installs (e.g. a prompt generated by the DSiloed UI for Claude Code to execute), skip the wizard:

silolink config --non-interactive \
  --host https://www.dsiloed.com \
  --tenant <tenant_id> \
  --user-sub <email-or-username> \
  --jwt <pre-issued-jwt> \
  [--mcp-port 3579] \
  [--projects-path /home/user/projects] \
  [--env dev]

All four of --host, --tenant, --user-sub, --jwt are required when --non-interactive is set. The JWT is written to ~/.silolink/config.json exactly as supplied — generate it via the same flow used by the user's MCP config dialog (or the agent's "Generate SiloLink Token" button).

~/.silolink/config.json schema:

FieldDescriptionDefault
hostDSiloed server URLhttps://www.dsiloed.com
tenant_idTenant enterprise identifierharmoniq
user_subYour username or email—
jwtJWT obtained at silolink config time (do not edit by hand)—
mcp_portLocal port for the MCP server3579
reconnect_interval_msBase reconnect delay5000
max_reconnect_attemptsMax reconnect tries20
agent_providerAI provider: claude, gemini, openaiclaude
claude_commandPath to Claude CLI binaryclaude
claude_working_directoryWorking directory for spawned sessions—
claude_auto_respawnAuto-respawn on idle/exitfalse
claude_idle_timeout_msIdle threshold before respawn30000
claude_session_promptDefault prompt for spawned sessionsRegister with SiloLink...

Example config file (after running silolink config):

{
  "host": "https://www.dsiloed.com",
  "tenant_id": "portablemind",
  "user_sub": "user@example.com",
  "jwt": "eyJhbGciOiJIUzI1NiJ9...",
  "mcp_port": 3579,
  "claude_command": "/home/user/.local/bin/claude",
  "claude_working_directory": "/home/user/projects",
  "claude_auto_respawn": true,
  "claude_idle_timeout_ms": 30000
}

Usage

Starting SiloLink

# Foreground (see logs in terminal)
silolink start

# Background (daemon mode)
silolink start --daemon

Managing the Daemon

silolink status          # Check connection state and active sessions
silolink sessions        # List active sessions
silolink stop            # Stop daemon (kills all Claude tmux sessions)

Launching Claude Sessions

# Launch from CLI
silolink launch
silolink launch -p "Work on the auth refactor"

# Launch from the DSiloed web UI
# Click "+" in the SiloLinks section of TeamChat

Reporting your own coding-agent usage

Post the token usage of a Claude Code / Gemini / Codex session you ran yourself, so its cost is tracked under your DSiloed user — ideal as a Stop/SessionEnd hook:

silolink report-usage --cli claude   # also: gemini | codex
silolink report-usage --cli claude --dry-run   # preview without posting

See docs/report-usage-hooks.md for hook setup across all three CLIs.

Connecting Claude Code (Manual)

Add SiloLink as an MCP server in your Claude Code settings (.claude.json or project .mcp.json):

{
  "mcpServers": {
    "silolink": {
      "command": "npx",
      "args": ["mcp-remote", "http://localhost:3579/mcp"]
    }
  }
}

Claude Session Launcher

SiloLink spawns and manages Claude Code sessions using tmux:

On-Demand (from DSiloed UI)

  1. Click + in the SiloLinks section of TeamChat
  2. Enter a session name and optional prompt → Launch Session
  3. Conversation appears immediately — "Starting session..." shows while Claude initializes
  4. Claude sends "Session ready!" when the poll loop begins (~20-30 seconds)

Auto-Launch (on inbound message)

When a message arrives on a SiloLink conversation with no active session:

  1. SiloLink posts "Restarting session..." immediately
  2. Spawns Claude Code in a new tmux session
  3. The triggering message is buffered and delivered once Claude registers
  4. Claude sends "Session ready" and enters the poll loop

Multi-Session

Each conversation gets its own tmux session. Multiple sessions run simultaneously.

# List active tmux sessions
tmux list-sessions | grep silolink

# Attach to watch Claude work
tmux attach -t silolink-claude-1774363497579

# Detach: Ctrl+B, D

Session Lifecycle

  • Launch → SiloLink posts "Starting session..." → spawns tmux → Claude registers → "Session ready!"
  • Messages → user sends message → SiloLink enqueues → Claude polls and responds
  • Delete → gear icon → "Delete SiloLink" stops the Claude session and deletes the conversation
  • Shutdown → silolink stop kills all tmux sessions and completes agent sessions on DSiloed

MCP Tools

ToolDescriptionBlocking
remote_registerRegister session, create/attach conversationNo
remote_load_contextLoad prior conversation history for session continuityNo
remote_notifyFire-and-forget messageNo
remote_askPost question, wait for replyYes
remote_pollNon-blocking check for next message (recommended)No
remote_check_messagesNon-blocking check for ALL pending messagesNo
remote_wait_for_commandBlock until next message (prefer remote_poll)Yes
remote_sessionsList active sessionsNo
remote_unregisterUnregister and clean upNo
remote_workspace_createCreate an isolated git worktree for this sessionNo
remote_workspace_claimClaim files (advisory soft-lock) with conflict detectionNo
remote_workspace_checkCheck file claims across all sessionsNo
remote_workspace_mergeMerge worktree branch back and clean upNo
remote_workspace_listList all active workspacesNo

remote_register

Input:  { session_name?: "my project", conversation_id?: 453 }
Output: { session_id, conversation_id, conversation_url }

When conversation_id is provided, attaches to that existing conversation (used by auto-launcher).

remote_load_context

Input:  { conversation_id?: 109, limit?: 50 }
Output: { success: true, conversation_id, resume_id, message_count, history: [...] }

Fetches prior conversation history and the last claude_resume_id from agent session metadata. Call after remote_register when attaching to an existing conversation to restore session continuity.

remote_poll (Recommended)

Input:  {}
Output: { success: true, message: "...", sender_name: "..." }
        — or —
        { success: true, pending: true }

Non-blocking. Safer than remote_wait_for_command — can't lose messages on cancellation.

remote_notify

Input:  { message: "Build completed successfully" }
Output: { success: true, message_id }

remote_ask

Input:  { question: "Should I proceed?", timeout?: 300 }
Output: { response: "Yes", sender_name: "your-username" }

Workspace Isolation

Multiple sessions can work on the same repo without conflicts using git worktrees:

Session A: feature/auth  ──►  ~/.silolink/worktrees/myapp/feature/auth/
Session B: feature/api   ──►  ~/.silolink/worktrees/myapp/feature/api/

Creating a Workspace

// Agent creates a worktree on session start
remote_workspace_create({ repo: "/home/user/myapp", branch: "feature/auth" })
// → { worktree_path: "~/.silolink/worktrees/myapp/feature/auth", branch: "feature/auth" }

// Claim files before editing (advisory — warns on conflicts, doesn't block)
remote_workspace_claim({ files: ["src/auth.ts", "src/middleware.ts"] })
// → { claimed: [...], conflicts: [] }

// Check what other sessions are working on
remote_workspace_check({})
// → { all_claims: [{ session_id: "session-B", files: ["src/api.ts"] }] }

// When done, merge back
remote_workspace_merge({})
// → { success: true, merged_branch: "feature/auth" }

Cross-Session Conflict Notifications

When session A claims files that session B already owns, both sessions receive automatic notifications via their message queues and DSiloed conversations.

Launcher Integration

The control channel launch command supports workspace params:

{ "type": "launch", "conversation_id": 123, "repo_path": "/home/user/myapp", "branch": "feature/auth" }

Provider Selection

Set agent_provider in ~/.silolink/config.json:

{ "agent_provider": "claude" }

Supported providers: claude (default), gemini, and codex (the OpenAI CLI; openai is accepted as an alias). Launch a specific provider with silolink launch -a codex, or per-session from the DSiloed UI.

Codex notes

The Codex launcher uses codex's native streamable-HTTP MCP support — it writes [mcp_servers.*] tables (url + http_headers = { Authorization = "Bearer <jwt>" } for the dsiloed server) to $CODEX_HOME/config.toml, with no npx mcp-remote bridge. The JWT lives in the per-env config on disk (like the claude/gemini configs), not in argv. CODEX_HOME points at a per-env dir (each daemon identity owns its own config) and the user's ~/.codex/auth.json login is copied in. Codex is launched as env CODEX_HOME=<home> codex … because tmux does not propagate caller env to panes on an already-running server (a bare codex would read the empty default ~/.codex and expose no MCP tools). Codex reads agent instructions from AGENTS.md — seed the SiloLink poll-loop section with silolink setup-claude-md ./AGENTS.md. MCP tools are named <server>__<tool> (e.g. silolink__remote_register).

Validated against codex-cli 0.141.0. Knobs (all overridable):

Config keyDefaultPurpose
codex_commandcodexCLI binary
codex_args["--dangerously-bypass-approvals-and-sandbox"]fresh-launch flags (auto-approve, no sandbox prompt)
codex_resume_enabledfalseuse codex resume <id> on respawn (off → fresh session, context restored via remote_load_context)
codex_session_prompt—override the launch prompt
codex_auto_respawn / codex_idle_timeout_ms / codex_idle_kill_after_msas Claude/Geminilifecycle

If a flag changes in a future codex version, the daemon's pane watcher surfaces error: unknown option … — adjust codex_args. The TUI ready-screen text in getReadyIndicator() is best-effort (a miss just falls back to the ready timeout, not a wedge).

Control Channel API

SiloLink subscribes to a tenant-scoped ActionCable control channel on startup. The DSiloed UI sends commands via REST:

EndpointMethodDescription
/api/v1/silolink/launchPOSTLaunch new session (creates conversation)
/api/v1/silolink/stopPOSTStop session (by conversation_id or all)
/api/v1/silolink/statusGETRequest SiloLink status
# Launch
curl -X POST /api/v1/silolink/launch \
  -d '{"name": "my-feature", "prompt": "Work on auth"}'

# Response
{"success": true, "conversation_id": 458, "message": "Launch command sent"}

CLAUDE.md Integration

Add to your project's CLAUDE.md:

## Remote Communication (SiloLink)

When the SiloLink MCP server is connected:

1. Register with `remote_register({ session_name: "<name>" })`.
   If a conversation_id is provided, pass it too.
2. **Session Continuity**: If you registered with an existing conversation_id,
   call `remote_load_context()` to load prior conversation history.
3. ALL communication MUST go through SiloLink — never write to terminal.
4. Send progress updates with `remote_notify()`.
5. When idle, use the poll loop:
   - Call `remote_poll()` — returns instantly
   - If `{ pending: true }`: sleep 3s, poll again
   - If message received: process it, notify result, resume
6. Use `remote_ask()` for questions needing immediate reply.

Poll Loop Pattern

// Register (use conversation_id if provided in launch prompt)
remote_register({ session_name: "portablemind", conversation_id: 123 })

// If attaching to existing conversation, load prior context
context = remote_load_context()
// Review context.history to understand what was discussed

// Poll loop
while (idle) {
  result = remote_poll()
  if (result.pending) { sleep(3s); continue }
  handle(result.message)
  remote_notify({ message: "Done: ..." })
}

Agent Sessions

Active SiloLink sessions appear in the Mission Control Agent Dashboard with:

  • Session name (matches conversation name)
  • Status: idle, working, waiting for human, error, completed
  • Heartbeats on every tool call
  • Conversation link for quick navigation

Permissions

SiloLink-spawned sessions run headlessly — no human at the keyboard. Permission prompts hang the pane, so the launcher pre-clears two gates so --dangerously-skip-permissions actually takes effect on every command (the flag alone is not enough on CC 2.x).

Pre-cleared automatically on each launch:

  1. Workspace trust dialog (per cwd) — written to ~/.claude.json under projects.<cwd>.hasTrustDialogAccepted.
  2. Bypass-permissions consent (user scope) — written to ~/.claude/settings.json as a top-level skipDangerousModePermissionPrompt: true.

The second one is the gotcha: without that consent on file, CC still re-prompts on the "dangerous pattern" class (rm -rf, sudo, ...) even when launched with --dangerously-skip-permissions and even when the in-session footer shows ⏵⏵ bypass permissions on. The footer just reflects mode; the consent is the gate. CC's internal check (Sd() in the binary) reads this key from userSettings / localSettings / flagSettings / policySettings — all at the top level of the settings object, not nested under permissions. The schemastore claude-code-settings schema doesn't document this key as of CC 2.1.x; the binary is authoritative.

Both writes are idempotent and only touch the specific keys involved — other settings (theme, plugins, permissions block, project MCP enablement, etc.) are preserved by a shallow merge.

Manual verification:

# Should print: True
python3 -c 'import json; print(json.load(open("'$HOME'/.claude/settings.json")).get("skipDangerousModePermissionPrompt"))'

If the value is missing or false, restart silolink — ClaudeLauncher.ensureBypassPermissionsConsent() runs on each session spawn and will write it.

What still prompts: CC has a hardcoded circuit breaker for catastrophic patterns (rm -rf /, rm -rf ~, etc.) that re-prompts regardless of settings. Those need a human response — see the send_keys control command for driving the prompt from the web UI.

For sessions launched outside silolink — if you're running claude by hand in a terminal:

  • Option A — Skip all prompts: claude --dangerously-skip-permissions. Requires the user-scope consent above (run silolink start once and it's set, or write the file manually).
  • Option B — Allow SiloLink tools only: { "permissions": { "allow": ["mcp__silolink__*"] } } in .claude/settings.json.

Technical Details

  • Authentication: HS256 JWTs (24h validity, auto-refresh every 12h)
  • Reconnection: Exponential backoff (1s → 60s cap)
  • Echo Prevention: Tracked outbound message IDs + prefix matching
  • Session Cleanup: Idle sessions cleaned after 1 hour, agent sessions completed on DSiloed
  • Multi-Session: Multiple tmux sessions, one per conversation
  • Message Buffering: Pre-registration messages delivered on session register
  • API Timeouts: 30-second abort on all DSiloed API calls
  • Map Validation: Session manager validates 3-map consistency every 5 minutes
  • Shutdown: Kills all Claude tmux sessions first, then completes agent sessions

Health Check

curl http://localhost:3579/health
# { "status": "ok", "sessions": 2, "cable": "connected" }

Development

pnpm dev start          # Dev mode (no build)
pnpm exec tsc --noEmit  # Type check
pnpm build              # Build
pnpm test               # Tests

Project Structure

src/
  index.ts                      # CLI entry point
  cli/
    commands.ts                 # CLI commands (start, stop, status, launch, config)
    daemon.ts                   # PID file management
  config/
    config-manager.ts           # ~/.silolink/config.json management
    jwt-generator.ts            # HS256 JWT generation + auto-refresh
  core/
    bridge.ts                   # Main orchestrator — startup, shutdown, control channel
    session-manager.ts          # Session registry with 3-map lookup + validation
    message-queue.ts            # Per-session inbound queue with acknowledgment protocol
    agent-launcher.ts           # AgentLauncher interface and types
    base-tmux-launcher.ts       # Shared tmux launcher base class
    claude-launcher.ts          # Claude Code launcher (tmux-based)
    gemini-launcher.ts          # Gemini CLI launcher (tmux-based)
    codex-launcher.ts           # Codex (OpenAI) CLI launcher (tmux-based)
    transcript-usage.ts         # Per-model token parsing (claude/gemini/codex)
    report-usage.ts             # End-user usage collect + post (Stop hooks)
    launcher-factory.ts         # Provider selection factory
    workspace-manager.ts        # Git worktree lifecycle, file claims, conflict detection
  cable/
    action-cable-client.ts      # ActionCable WebSocket client with auto-reconnect
    subscription-manager.ts     # Conversation subscriptions, echo prevention, message buffering
  api/
    dsiloed-client.ts           # REST client for DSiloed API (30s timeouts)
  mcp/
    server.ts                   # MCP server (Streamable HTTP on Express)
    tools/
      register-tools.ts         # All 14 MCP tool definitions
  types/
    index.ts                    # Shared TypeScript interfaces

License

MIT

↑↓ navigate openesc close