From stdio to Anywhere:
Securely Exposing OpenClaw
as a Remote MCP Server

Nick Taylor ยท @nickytonline ยท MCP Dev Summit Toronto 2026

Nick Taylor @nickytonline

What is OpenClaw?

Open source, self-hosted personal AI assistant. One Gateway process, on your own hardware.

Chat, agents, channels, approvals, all under your control.

Nick Taylor @nickytonline

OpenClaw Auth Modes Pre-Mid February

Gateway auth modes: none, password, token.

Needed it reachable beyond your machine? Common pattern: Tailscale Serve (tailnet-only), or just stay on loopback, local only.

Nick Taylor @nickytonline
GitHub PR #15940: feat(gateway): add trusted-proxy auth mode, merged
Nick Taylor @nickytonline

Trusted-proxy auth mode

A generic abstraction: the OpenClaw Gateway trusts identity headers from a proxy you configure, checking, among other things:

  1. the source IP is in gateway.trustedProxies
  2. the required headers are present
  3. the user header names an allowed user
Nick Taylor @nickytonline

What's an identity-aware proxy (IAP)?

  • Checks identity and policy on every request, not just once at login
  • No VPN, no trust based on which network you're on
  • Great at securing internal resources
Nick Taylor @nickytonline

What's an identity-aware proxy (IAP)?

Diagram: You (browser/client) through an identity-aware proxy, which checks identity against an Identity Provider and policy, then allows access to internal resources (resource 1, resource 2, ..., resource n) or denies access
Nick Taylor @nickytonline

Trusted-proxy auth mode: not just IAPs

Any proxy that authenticates and forwards identity headers qualifies.

Some concrete implementations:

  • Caddy + OAuth plugin
  • Cloudflare Access
  • nginx + oauth2-proxy
  • Pomerium
  • Traefik forward auth
Nick Taylor @nickytonline

My Homelab OpenClaw

QR code for https://claw.maisonlab.dev
claw.maisonlab.dev
Nick Taylor @nickytonline

Wait... isn't this an MCP conference?

OpenClaw already has an MCP server

openclaw mcp serve exposes conversations, messages, events and approvals as tools over stdio.

docs.openclaw.ai/cli/mcp/serve

Nick Taylor @nickytonline

OpenClaw already has an MCP server

Example stdio client config, straight from OpenClaw's own docs. Generic, works with most MCP clients:

{
  "mcpServers": {
    "openclaw": {
      "command": "openclaw",
      "args": ["mcp", "serve"]
    }
  }
}
Nick Taylor @nickytonline

Gap 1: only if your harness can spawn it

stdio means the client has to spawn openclaw mcp serve itself, as a local subprocess, wherever the client runs.

ChatGPT.com, Claude.ai, and other hosted clients have nowhere to spawn anything.

Nick Taylor @nickytonline

OpenClaw remote Gateway != remote MCP

openclaw mcp serve --url wss://... still runs MCP over stdio, locally. Only the hop to the Gateway is remote, over OpenClaw's own WebSocket protocol.

It's still stdio MCP, just more complex.

Nick Taylor @nickytonline

Software and secrets on every machine

  • install and update the OpenClaw CLI
  • distribute and rotate a Gateway secret, the same one for everyone

We want it secured, per user. Remote MCP + OAuth is built for that.

Nick Taylor @nickytonline

Don't fork OpenClaw. Wrap it.

claw-mcp: a small Streamable HTTP server that lives outside OpenClaw.

  • Spawns the unmodified openclaw mcp serve and proxies its tools
  • Adds read-only workspace_* tools for the agent's files
  • Sits behind an identity-aware proxy, which does all the OAuth
Nick Taylor @nickytonline

Demo: the architecture

Diagram: ChatGPT or Claude over HTTPS with MCP OAuth to an identity-aware proxy (email policy, per-tool deny), then claw-mcp (stateless Streamable HTTP), then over stdio to openclaw mcp serve (one shared child), then over ws://127.0.0.1 with the gateway password to the OpenClaw Gateway
Nick Taylor @nickytonline

Wrapping a stdio tool call

async callTool(params: CallToolRequest["params"]) {
  // Exclusion is enforced here too, not just hidden from tools/list.
  const tool = (await this.listTools()).find((t) => t.name === params.name);
  if (!tool) throw new ProtocolError(...);
  ...
  const client = await this.#connect();
  return client.callTool(
    { name: params.name, arguments: params.arguments },
    // events_wait may block for up to 300s upstream.
    { timeout: this.#callTimeoutMs, toolDefinition: tool },
  );
}
Nick Taylor @nickytonline

Policy at the edge

- from: https://clawmcp.demo.iamdeveloper.com
  to: http://openclaw-gateway:3000
  mcp:
    server: {}
  policy:
    - allow:
        and:
          - email: { in: [nick@nickyt.co, ntaylor@pomerium.com] }
    - deny:
        or:
          - mcp_tool: { in: [permissions_respond] }

The tool still shows up in the list, but every call to it is denied.

Nick Taylor @nickytonline

Demo

Gap 2: identity stops at the wrapper

In trusted-proxy mode the Gateway accepts:

  • identity forwarded by a trusted proxy, or
  • a password from loopback

mcp serve can't send proxy headers, so the bridge sits on loopback with the password.

The proxy verifies every user. The Gateway sees one operator.

Nick Taylor @nickytonline

The fix belongs in OpenClaw

openclaw/openclaw#161168: let mcp serve send identity on connect, like the TUI and gateway call already do.

openclaw mcp serve --url wss://gateway.internal \
  --header x-forwarded-user=<verified user> \
  --header x-forwarded-for=<client ip>

I opened the issue, then a draft PR. With it, the wrapper can run one bridge per user.

Nick Taylor @nickytonline

Caveats, for now

  • I have a POC draft PR.
  • Device pairing: each new user's bridge needs one admin approval. Trusted-proxy auto-approval only covers browser and native UI clients.
  • So I opened a follow-up, linked from #161168: opt-in auto-approval for mcp serve bridges, with the same scope cap as browsers.
Nick Taylor @nickytonline

Where this could go

OpenClaw's Gateway could be a remote MCP server itself, with no stdio wrapping.

Nick Taylor @nickytonline

Open source, start to finish

  1. Trusted-proxy auth mode (#15940). The community made it production-grade.
  2. mcp serve couldn't pass identity: #161168 and a draft PR.
  3. Building this found the next gap: auto-approval for bridges.
Nick Taylor @nickytonline

Resources

Nick Taylor @nickytonline

Resources

Nick Taylor @nickytonline

Thank You!

Nick Taylor ยท @nickytonline

TIMING PLAN, 18:15 total (25:00 slot, ~6:45 buffer) Spine: reach it from anywhere -> hosted AI clients -> as you. Intro ................ slides 1-8 ~4:15 (ends ~4:15) OpenClaw + goal, auth modes, trusted-proxy PR + mode, what's an IAP (+ diagram), other implementations My Homelab OpenClaw .. slide 9 ~0:45 (ends ~5:00) homelab QR + quick live demo: log in once on the web, no token paste Gap 1 + the wrapper .. slides 10-19 ~4:15 (ends ~9:15) STEP 2 begins, opens on the "isn't this an MCP conference?" beat DEMO ................. slide 20 ~4:30 (ends ~13:45) droplet: remote MCP, ends on the gap Gap 2 + the fix ...... slides 21-22 ~2:00 (ends ~15:45) STEP 3 begins Wrap-up .............. slides 23-28 ~2:30 (ends ~18:15) caveats, where this could go, contributing [0:15]

[0:45] Keep the screenshot from the original slide. Preamble out loud here: been running OpenClaw since January, and as always, reached for Pomerium to secure it. Then state the spine verbally: reach it from anywhere -> let hosted AI clients use it -> as you, not a shared account. The homelab walkthrough and the demo each pay off one step; the gap slides call back to it.

[0:30] Tailscale Serve is a network-layer workaround, not a fourth auth mode: it keeps you off the open internet but doesn't solve the double-login gap. Say the gap out loud instead: behind a proxy that already verified who I was, the Gateway still wanted its own pasted token and device pairing. Sets up the motivation: let the Gateway trust a proxy that already verified who I am.

[0:15] The fix: my PR, merged upstream. I landed it (#15940); the community added per-user scopes, loopback guards and device auto-approve.

[1:15] Step 1 of the spine: reach it from anywhere. Plant identityScopes (per-user scopes) and device auto-approve: both come back in the caveats. "It's not just my feature anymore." Next: what kind of proxy is that?

[0:30] This is the kind of proxy the mode is built for. Next: the shape.

[0:15] The generic shape. Next: it's not just IAPs, here's who else qualifies.

[0:30] Not hypothetical: straight from the docs' "Proxy setup examples," full config snippets, plus Cloudflare's own topology page. The community's since generalized this exact list themselves (#136686) as the standard identity-aware proxy set the Gateway already supports. Pomerium's what I demo today; it's also an MCP gateway (MCP OAuth for hosted clients, policy down to a single tool), which matters in the demo.

[0:45] My real homelab OpenClaw, behind Pomerium. The QR code goes to the web route. Leave this slide up for a few seconds while people get their phones out, then live-demo it in the browser: log in once with Pomerium, and the Gateway trusts the proxy, no token paste. They'll keep trying the QR while you talk. Before the talk: confirm the route uses an explicit email allow list, test with a non-allowed account, and accept that the hostname will be public from now on.

[0:15] Let it land. Nine slides in, no M, C, or P yet. Pivot: turns out OpenClaw already ships one.

[0:15] Step 2 of the spine: hosted AI clients. Next: wiring it up.

[0:20] Not Claude-specific: this is the shape most stdio MCP clients converged on. It just works, today, zero extra code. Next slide's the catch.

[0:30] Per spec, the client launches the server as a subprocess, full stop: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio That's true even behind an SSH wrapper; the client still only spawns `ssh` locally. Claude Desktop and Claude Code CAN do this (local stdio is their whole model); the gap is specifically the hosted chat products.

[0:15] Source: docs.openclaw.ai/cli/mcp/serve, "Remote Gateway Setup" (--url plus --token-file or --password-file). The remote hop is OpenClaw's WebSocket protocol, not MCP. Hosted clients still can't use it, but slide 13 already made that point. Next: what that costs you.

[0:20] No OAuth, no per-user identity, no per-tool policy: one shared secret. Every laptop is a new place the CLI can go stale and the secret can leak; a remote server has one place to update and one place to enforce auth. Sets up claw-mcp: wrap it, don't fork it.

[0:45] Built on my MCP TypeScript template. Stateless MCP (2026-07-28 spec) over HTTP; the stdio child stays up because it holds the live event queue. If asked: events_wait can block 300s, so the Pomerium route timeout is 330s.

[0:30] Don't dwell on the password line yet. It's the gap after the demo.

[0:30] The whole wrapper in one method: look up the tool, reconnect to the stdio child if needed, forward the call. Hidden tools are rejected here too, not just filtered from tools/list. The long timeout is for events_wait. Next: policy at the edge.

[0:30] The slide people photograph.

[4:30] Run-sheet. Sign in to both profiles BEFORE the talk. 1. claude mcp add --transport http clawmcp https://clawmcp.demo.iamdeveloper.com/mcp 2. conversations_list, then messages_read on a Slack/Discord session 3. workspace_query "<something in MEMORY.md>" 4. permissions_respond: Pomerium denies it 5. THE GAP: switch to the nick@nickyt.co profile and run messages_send. It works. "Pomerium knows this is Nick. OpenClaw has no idea." Fallback: recorded video. Web UI chats (agent:main:main) don't show in conversations_list; only channel-routed sessions do.

[0:45] Step 3 of the spine: as you. This is why Nick could send that message. The issue I opened next.

[0:45] You don't invent the user header. claw-mcp verifies the proxy's signed JWT first (Pomerium's, in my setup), then writes it.

[0:45] The demo never hit pairing: loopback devices auto-approve, but loopback can't carry identity. If asked "isn't that a hole?": the same user's browser already auto-approves with the same scopes, so it adds no authority. Fill in the follow-up issue number.

[0:30] Trusted-proxy mode already gives every request a verified user, so the IAP handles MCP OAuth and OpenClaw applies that user's scopes per request. If asked what's hard: event cursors (events_wait) move server-side, and device-less MCP requests need a scope rule like the Control UI's. #161168 stays useful either way: it's what a local mcp serve (Claude Code on a laptop) needs to reach a remote Gateway behind an IAP.

[0:30] Add the follow-up issue number.

[0:15] All links are in the published deck; don't read them out. Lead with #15940 (where the story started) and #161168 (where it ends).

[0:15] Pomerium links if asked: https://www.pomerium.com/docs/guides/openclaw and https://www.pomerium.com/docs/capabilities/mcp. Point at claw-mcp and the template. Make sure claw-mcp is public and the per-user mode is pushed.

[0:15]