[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6N0BJRNMJSJQYG7Y5VE2NZJ":3,"skill-same:01M6N0BJRNMJSJQYG7Y5VE2NZJ":96},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":45,"linkOnly":46,"markdownContent":47,"markdownBody":48,"frontMatter":49,"refName":68,"commitSha":69,"skillDirectory":70,"forksCount":71,"repositoryHtmlUrl":72,"repositoryHomepageUrl":6,"ownerHtmlUrl":73,"ownerBlogUrl":74,"ownerDescription":75,"updatedAt":76,"install":77,"resources":89},"01M6N0BJRNMJSJQYG7Y5VE2NZJ","remote-mcp",null,"Publish the user's own gbrain over MCP so other devices, desktop apps and\ncloud agents can reach it. `gbrain mcp expose` installs and signs in\nTailscale, publishes the running `gbrain serve --http` on the tailnet\n(HTTPS, tailnet-only by default; Funnel only when a client lives in a\nvendor cloud), keeps the server alive as a user service, and hands back the\nMCP URL. Then register one least-privilege client per consumer, choose\nnative OAuth\u002FPKCE or a private machine handoff for that client, and verify\nthe connection inside the intended harness.","garrytan\u002Fgbrain","garrytan","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F19957?v=4","01M6DJCCDDG53MTYYRTXAXFG17","Garry Tan","01M6MZQZCEN3RRMD33E1X9ZZR9",32847,"MIT","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fplugin\u002Fskills\u002Fremote-mcp\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],"MCP","remote mcp","remote","when","brain","desktop apps","native","server","publish","bot","mcp server","URL","apps","access","connect","muse","only","default","choose","mcp access","back","2026-11-10 10:12:30",false,"---\nname: remote-mcp\ndescription: |\n  Publish the user's own gbrain over MCP so other devices, desktop apps and\n  cloud agents can reach it. `gbrain mcp expose` installs and signs in\n  Tailscale, publishes the running `gbrain serve --http` on the tailnet\n  (HTTPS, tailnet-only by default; Funnel only when a client lives in a\n  vendor cloud), keeps the server alive as a user service, and hands back the\n  MCP URL. Then register one least-privilege client per consumer, choose\n  native OAuth\u002FPKCE or a private machine handoff for that client, and verify\n  the connection inside the intended harness.\ntriggers:\n  - \"use my brain over mcp\"\n  - \"serve my brain over mcp\"\n  - \"expose my brain over mcp\"\n  - \"gbrain mcp server\"\n  - \"remote mcp access to my brain\"\n  - \"put my brain on tailscale\"\n  - \"connect grok bot to my brain\"\n  - \"connect muse to my brain\"\n  - \"connect claude desktop to my brain\"\n  - \"reach my brain from my phone\"\n  - \"gbrain mcp expose\"\ntools:\n  - exec\nmutating: true\n# exempt: this skill changes host networking\u002Fservice state and never answers\n# a knowledge question, so there is nothing to look up in the brain first.\nbrain_first: exempt\nwhen_to_use: \"Use when the user asks: \\\"use my brain over mcp\\\", \\\"serve my brain over mcp\\\", \\\"expose my brain over mcp\\\", \\\"gbrain mcp server\\\", \\\"remote mcp access to my brain\\\".\"\n---\n\n# Remote MCP — use your brain from anywhere\n\n> The brain already runs on the user's computer. This skill makes it reachable\n> over MCP from their other devices and from the agents they use elsewhere,\n> without moving the data and without putting a database on the public\n> internet. Tailscale is the default transport; publishing is one command,\n> access is one scoped grant per client.\n\n## Contract\n\nThis skill guarantees:\n- **Tailscale by default.** `gbrain mcp expose` publishes the local server\n  with `tailscale serve` (HTTPS on the tailnet, nothing public). ngrok and\n  cloud hosts stay documented alternatives, never the first suggestion.\n- **Funnel is explicit.** Cloud agents whose runtime is not on the tailnet\n  (Grok Bot, Muse, ChatGPT, Claude.ai \u002F Cowork, Perplexity) need\n  `--funnel`, which makes the same `*.ts.net` name publicly reachable. Say\n  so before running it; the endpoint is then protected by gbrain's OAuth \u002F\n  bearer auth and scoped grants, not by the network.\n- **Consent before system changes.** Installing Tailscale, running\n  `tailscale up`, writing a serve\u002Ffunnel config and creating a user service\n  are host-state changes. Show the operator the printed plan and get a yes\n  before passing `--yes`.\n- **Engine-free.** `gbrain mcp expose` never opens the database. On a PGLite\n  brain the running server owns the single-writer lock, so every later\n  provisioning step goes through the server's authenticated admin API\n  (`--admin-token-file`), never a second process on the database.\n- **No secrets in chat.** The admin token lives in\n  the receipt's `admin_token_file` (normally `~\u002F.gbrain\u002Fserve\u002Fadmin-token`);\n  machine and confidential OAuth credentials land in a private\n  `--credentials-out` file. Public PKCE has no secret. Quote redacted\n  receipts; deliver a requested owner login link privately and unopened.\n- **Owner authority is separate.** The expose-managed wrapper loads its\n  token file as `GBRAIN_ADMIN_BOOTSTRAP_TOKEN`; that file authorizes\n  `gbrain mcp admin` and `mcp grant` against that server. An ordinary MCP\n  token, client secret, or OAuth `admin` scope does not. With `--no-service`,\n  use the independently running server's actual configured owner credential;\n  creating an expose token file does not configure that process.\n- **Least privilege.** One client per consumer, `memory-writer` for ordinary\n  memory use or `memory-reader` for read-only access. Broader access requires\n  the user's request. Never `operator` \u002F `full` \u002F `admin` to make a convenience\n  check pass.\n- **Server checks are not native activation.** `gbrain mcp verify` accepts a\n  private machine handoff and checks the access that grant permits. Native\n  OAuth is verified through the native client's authenticated connection.\n  A generated file or server probe does not prove that client loaded its tools.\n\n## Decision table — who is connecting?\n\n| Client | Shape | Command |\n| --- | --- | --- |\n| Your own devices: Claude Desktop, Claude Code \u002F Codex \u002F opencode on another laptop, phone apps joined to the tailnet | tailnet-only HTTPS (default) | `gbrain mcp expose --dry-run` (preview; the run itself is Phase 2, after consent) |\n| Cloud agents running in a vendor's cloud: Grok Bot, Muse, ChatGPT connector, Claude.ai \u002F Cowork, Perplexity Computer | public HTTPS on the same `*.ts.net` name | `gbrain mcp expose --funnel --dry-run` (preview; Phase 2 after consent) |\n| Cloud agent whose runtime you can join to your tailnet (userspace `tailscaled`, ephemeral auth key) | tailnet-only | Advanced, unverified, not automated — see the [remote MCP guide](https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Fguides\u002Fremote-mcp.md) |\n| Local agents on the same machine (Claude Code, Codex, opencode) | loopback — no Tailscale needed | Postgres: `gbrain bootstrap harness --yes --port 3131`. PGLite: mint the token BEFORE the service runs (`gbrain auth create local-agents --scopes read,write`, plus `--member \u003Cid>` on a brain that serves several people — before `gbrain mcp expose`, or while the service is briefly stopped) and pass `gbrain bootstrap harness --yes --port 3131 --token \u003Cvalue>`; OR use the scoped path `gbrain mcp grant \u003Cname> --harness \u003Cid> --profile memory-writer --source default --url http:\u002F\u002F127.0.0.1:3131\u002Fmcp --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --credentials-out \u002Fprivate\u002F\u003Cname>.json` then `gbrain connect http:\u002F\u002F127.0.0.1:3131\u002Fmcp --harness \u003Cid> --credentials-file \u002Fprivate\u002F\u003Cname>.json --install` (MCP wiring only, no per-turn hooks) |\n| Thin client only (this machine has no brain) | — | Stop: run this skill on the brain host |\n\nGrok Bot and Muse users with no always-on machine keep the in-agent local\ninstall described in [setup](..\u002Fsetup\u002FSKILL.md) as the alternative.\n\nNetwork reach and authentication are separate choices. A vendor name or\ntailnet\u002FFunnel mode does not select the credential flow: inspect the intended\nclient's native OAuth settings or supported private-handoff adapter in Phase 3.\n\n## Phase 1 — Detect\n\n```bash\ngbrain engine status --json\ngbrain mcp expose --status --json\n```\n\n- `thin_client: true` → the brain lives elsewhere. Stop and say the command\n  runs on the brain host.\n- `effective_engine: \"pglite\"` → note the single-writer rule (below) before\n  continuing; `\"postgres\"` → concurrent local commands stay fine.\n- `--status` reports `status: \"not_exposed\"` (exit 2 with `--json`) →\n  Phase 2. `status: \"exposed\"` → skip to Phase 3 with the printed\n  `mcp_url`. `status: \"pending\"` → the certificate or service is still coming\n  up; re-run `--status` in a minute before changing anything.\n  `reason: \"leftovers_without_receipt\"` (exit 1) → an interrupted run left\n  the wrapper, the unit \u002F service or a `:443` handler behind; run the exact\n  `gbrain mcp expose --remove --yes …` line from `next_actions` (it carries\n  `--force` only when a handler stands alone), then Phase 2.\n- The brain is already reachable over HTTPS by other means (an existing\n  `serve --http` behind ngrok \u002F a reverse proxy, or a brain hosted elsewhere) →\n  do NOT run `gbrain mcp expose`. Go straight to hosted access: choose the\n  client's connection method and register it against that endpoint (Phase 3\n  with its URL and owner authority), then configure the client (Phase 4).\n- Ask which clients should reach the brain (the decision table decides\n  tailnet vs `--funnel`). Do not ask questions the user already answered.\n\n## Phase 2 — Publish\n\nPreview first, then run with consent:\n\n```bash\ngbrain mcp expose --dry-run            # prints the plan; changes nothing\ngbrain mcp expose --yes                # your own devices (tailnet only)\ngbrain mcp expose --funnel --yes       # a cloud agent must reach it\n```\n\nOptions: `--port N` (default 3131), `--surface verbs|starter|full` (default\n`full`, mirrors `serve --http`), `--enable-dcr`, `--no-tailscale` (publish\nnothing; just install the service), `--no-service` (publish only; the\noperator runs `serve` themselves), `--no-install` (fail instead of\ninstalling Tailscale), `--force` (take over a serve handler that already\npoints at another local port), `--json`.\n\nWhat the command does, in order: plan (refuses up front when anything —\nHTTP or not — already listens on the port and `--no-service` is absent, and\nwhen `--no-tailscale` is passed over a receipt that records a live tailnet\nmapping) → consent → find or install the `tailscale` binary → sign in if\nneeded (Linux: `sudo tailscale set --operator=$USER`, then `sudo tailscale up`\n— `sudo` only in front of a system-installed binary; the operator preference\nis persistent and `--remove` does not revoke it; macOS: `tailscale up`) →\nread the machine's tailnet DNS name and pre-check that HTTPS certificates are\nenabled (and, with `--funnel`, that the node has the Funnel capability) →\n`tailscale serve` \u002F `funnel` → ensure the admin token →\nwrite the wrapper and a launchd agent \u002F systemd user unit → poll local and\ntailnet `\u002Fhealth` → write the receipt `~\u002F.gbrain\u002Fserve\u002Fexpose.json`. With\n`--json` every step is a named check (`plan`, `consent`, `tailscale.binary`,\n`tailscale.login`, `tailscale.identity`, `tailscale.publish`, `admin_token`,\n`service`, `verify.local`, `verify.tailnet`, `receipt`); the health checks\nare `verify.local` and `verify.tailnet`, not `verify`.\n\nRelay every prompt the command surfaces:\n\n| Output | What you do |\n| --- | --- |\n| Tailscale login URL printed by `tailscale up` | Give the user the URL, wait for them to sign in; on exit 2 (`tailscale_login_pending`) re-run the exact command the output prints |\n| `declined` (exit 2, \"Nothing changed.\") | The operator said no at the prompt. Nothing was installed or published; pass `--yes` only after they confirm the printed plan |\n| `tailscale_https_not_enabled` — pre-check, exit 2, nothing published (`CertDomains` empty) | Enable MagicDNS + HTTPS Certificates at `https:\u002F\u002Flogin.tailscale.com\u002Fadmin\u002Fdns`, then re-run the printed command. The same reason with exit 1 means the `tailscale serve` call itself refused for the same cause — same fix |\n| `tailscale_funnel_not_enabled` — pre-check, exit 2, nothing published (the node lacks the Funnel capability) | Enable the `funnel` node attribute in the tailnet policy (`https:\u002F\u002Flogin.tailscale.com\u002Fadmin\u002Facls`; see `https:\u002F\u002Ftailscale.com\u002Fkb\u002F1223\u002Ffunnel`), then re-run. Exit 1 with this reason is the post-`funnel` classified form — same fix |\n| `tailscale_needs_operator` | Run `sudo tailscale set --operator=$USER`, then re-run |\n| `tailscale_unknown` (exit 1) — `tailscale serve --bg` did not finish within 60s | The CLI is waiting on the operator (an enablement step the pre-checks could not see). Have them run the printed `tailscale serve --bg \u003Cport>` by hand to see its prompt, then re-run |\n| `tailscale_needs_login` | Linux: `sudo tailscale set --operator=$USER` then `sudo tailscale up`; macOS: `tailscale up` or open the Tailscale app and sign in; then re-run |\n| `tailscale_login_manual` (exit 2) — \"tailscale at \u003Cpath> is not a system install, so gbrain will not run it with sudo\" | The `tailscale` on `PATH` is user-writable (e.g. `~\u002F.local\u002Fbin`); gbrain never runs it as root. The printed command names that path — `sudo \u003Cpath> set --operator=$USER && sudo \u003Cpath> up` (a bare `sudo tailscale` would not find a binary outside sudo's `secure_path`). Running it is the operator choosing to trust that binary: say so, let them run it themselves, then re-run the printed command |\n| `tailscale_receipt_present` (exit 1, `plan` fails) — \"This brain is already published on your tailnet at \u003Curl>\" | `--no-tailscale` was passed while a live Serve\u002FFunnel mapping is on record. Run `gbrain mcp expose --remove --yes` first, or drop `--no-tailscale` |\n| `tailscale_daemon_not_running` (exit 2) | macOS `open -a Tailscale`; Linux `sudo systemctl enable --now tailscaled`; then re-run |\n| `foreign_serve_config` — \"tailscale serve already proxies :443 to …\" (exit 1) | Show the user what the existing handler proxies (`tailscale serve status`); only `--force` on their explicit yes. A handler owned by another terminal's foreground `tailscale serve` session cannot be taken over — it must be stopped in that terminal |\n| `foreign_listener` — \"Something already answers on 127.0.0.1:\u003Cport> and no expose receipt claims it\" (exit 1, refused BEFORE anything is published; ANY listener counts — an accepted TCP connect from a non-HTTP service as much as a 404) | Stop the other process, pick another `--port`, or pass `--no-service` to publish it as-is. If it is a gbrain server left by an interrupted run, `gbrain mcp expose --remove --yes` first (receipt-less recovery) |\n| `no_brain_config` (exit 1, `plan` fails) — \"No brain is configured on this host (gbrain init first), so a service would only crash-loop\" | Run `gbrain init` on this host first, then re-run; or pass `--no-service` to only publish a server the operator starts themselves. Never install the service around a missing brain |\n| `pglite_lock` warn — \"a live process holds this PGLite brain (pid N, …)\" | Tell the user which process holds the lock; the service cannot start until it exits. Stop it (or route to [postgres-adopt](..\u002Fpostgres-adopt\u002FSKILL.md) for concurrent use), then `gbrain mcp expose --status` |\n| \"could not read tailscale serve status\" — `tailscale.publish` fails (publish: exit 1 `tailscale_\u003Ckind>`, nothing published; `--status`: exit 1; `--remove`: exit 1 `tailscale_serve_status_unreadable` — with a receipt, receipt + wrapper kept; without one, the service is still removed but the wrapper is kept as the corroboration the re-run needs) | The command fails closed instead of guessing. Run `tailscale serve status --json`, apply the classified fix (operator \u002F daemon \u002F login), then re-run the same command |\n| `handler_not_removed` (`--remove`, exit 1) — \"the tailscale handler for port \u003Cport> is still present\" \u002F \"could not be confirmed gone\" | The `off` ran but gbrain's handler survived (or the re-read failed). The service is already gone; the receipt and wrapper were kept on purpose. Run `tailscale serve status`, fix what it reports, then `gbrain mcp expose --remove --yes` again — never `serve reset` |\n| `verify.tailnet: warn` — \"this host cannot resolve \u003Cname>\" (exit 0) | MagicDNS is off on the brain host itself; `tailscale set --accept-dns=true`, then `gbrain mcp expose --status`. Other devices may already reach the server; do not treat it as a certificate wait |\n| `service: manual` (no supervisor: cloud sandbox, container) | Relay the printed foreground and `nohup … &` commands; this is a documented outcome, not a failure |\n| `verify.local: warn` (exit 2, `local_health_timeout`) | The handler is published, the service installed and the receipt written — this exit 2 is NOT \"nothing published\"; `--status` \u002F `--remove` find them. `\u002Fhealth` did not answer within the wait: check `~\u002F.gbrain\u002Fserve\u002Fserve.err`, then `gbrain mcp expose --status` |\n| `verify.tailnet: pending` (exit 2, `tailnet_health_pending`; `--status` reports it with exit 1) | Same: handler, service and receipt are already in place. First certificate issuance can take a minute; `gbrain mcp expose --status` later |\n| `confirmation_required` (exit 2) | Non-TTY run without `--yes`. Nothing changed; show the printed plan, relay the payload's `user_message`, and run its `fix.command` (it adds `--yes`) only after the operator agrees |\n| `tailscale_no_dns_name` (exit 1) — the node has no MagicDNS name | Enable MagicDNS + HTTPS Certificates at `https:\u002F\u002Flogin.tailscale.com\u002Fadmin\u002Fdns`, then re-run; nothing was published |\n| `tailscale_missing` (exit 1, `--no-install`) \u002F `tailscale_unsupported_platform` \u002F `tailscale_install_failed` (exit 1) | Tailscale is not installed and was not (or could not be) installed. Relay the printed install command or `https:\u002F\u002Ftailscale.com\u002Fdownload`, have the user sign in, then re-run |\n| `tailscale_publish_unconfirmed` (exit 1) — `serve --bg` exited 0 but the re-read shows no handler (or could not be read) | Run `tailscale serve status`; `gbrain mcp expose --remove --yes` clears a handler for the port without a receipt (`--force` when no wrapper or service of gbrain's exists yet), then re-run |\n| `service_install_failed` (exit 1) | The handler IS published and the receipt written with `service.state: stopped`. Read the error and `~\u002F.gbrain\u002Fserve\u002Fserve.err`, fix, re-run; or `gbrain mcp expose --remove --yes` |\n| `status_unhealthy` (`--status`, exit 1) | A named check failed (`tailscale.publish`, `service`, `verify.local`); apply that row, then `gbrain mcp expose --yes` repairs |\n| `recovered_without_receipt` (`--remove`, exit 0) | The receipt-less recovery finished: report what was removed and what was left (the admin token stays) |\n\nNever work around a classified error by editing Tailscale state by hand;\napply the printed fix and re-run.\n\n## Phase 3 — Choose the client flow and owner authority\n\nUse the successful expose receipt's `mcp_url` and `admin_token_file`; substitute\nthem for the example URL\u002Fdefault path below, including when `GBRAIN_HOME` is\ncustomized. Run owner commands on the host or in a separately authorized admin\nharness with that server's protected credential. An ordinary connecting client\nhands these steps to that administrator; its OAuth scopes cannot grant owner\nauthority. Follow [mcp-access](..\u002Fmcp-access\u002FSKILL.md) and\n[MCP administration](https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Fmcp\u002FADMIN.md) for owner actions and recovery.\n\nTo open the dashboard:\n\n```bash\ngbrain mcp admin login-link --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --json\n```\n\nDeliver the single-use link privately without fetching it. The plain admin URL\ndoes not authenticate. For pending native consent, add `--oauth-request ID`\nwith the exact opaque ID from the client's browser request; the owner then\nreviews consent separately. Expired requests or a restart require restarting\nauthorization in the native client.\n\n| Intended connection | Register | Deliver |\n| --- | --- | --- |\n| Native public PKCE | `mcp admin register`, method `none`, exact client callback | Native OAuth metadata; no client secret |\n| Native confidential PKCE | `mcp admin register`, actual `client_secret_post` or `client_secret_basic`, exact callback | Private `OAuthClientSetup` via `--credentials-out` |\n| Machine credentials \u002F managed bearer configuration | `mcp grant --harness ID` | Private machine handoff for `gbrain connect` |\n\nFor native OAuth, obtain the actual redirect URI and authentication method from\nthe intended harness. `CLIENT_REDIRECT_URI` below must be replaced with that\nexact URI; it is not a suggested callback:\n\n```bash\ngbrain mcp admin register native-example --redirect-uri 'CLIENT_REDIRECT_URI' \\\n  --token-endpoint-auth-method none --profile memory-writer --source default \\\n  --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --dry-run --json\n```\n\nReview the preview, then repeat without `--dry-run`. For confidential PKCE, use\nthe client's actual POST\u002FBasic method and add `--credentials-out PRIVATE_FILE`\nbefore creation. Native registration creates authorization-code and refresh\ngrants, not a machine token. Retrieve instructions using its returned ID:\n\n```bash\ngbrain mcp admin setup CLIENT_ID --harness generic --flow authorization-code \\\n  --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --json\n```\n\nUse the actual supported adapter ID when known; `generic` gives manual native\nsettings instructions. The native client initiates OAuth and owns its verifier.\nAn `OAuthClientSetup` is not a machine handoff for `gbrain connect` or\n`gbrain mcp verify`. Keep DCR off unless the owner explicitly chooses it; manual\nowner registration works without `--enable-dcr`.\n\nFor a client using a private machine handoff, preview through the same running\nserver's admin API:\n\n```bash\ngbrain mcp grant agent-example --harness grok-bot --profile memory-writer \\\n  --source default \\\n  --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token \\\n  --credentials-out \u002Fprivate\u002Fagent-example.json --dry-run --json\n```\n\n- `--harness` is the real adapter id (`gbrain mcp adapters`): `grok-bot`,\n  `muse`, `claude-desktop`, `codex`, `claude-code`, `opencode`, ...\n- Review the preview, then repeat without `--dry-run` to create the client.\n- Profiles (`memory-reader`, `memory-writer`, `coding-agent`, `operator`,\n  `delegating-agent`, `full`) and delegation limits are defined in\n  [hosted harness access](https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Fguides\u002Fhosted-harness-access.md);\n  default to `memory-writer`, or `memory-reader` when only reading is requested.\n- The receipt is redacted; the credentials file is 0600. Move it to the\n  client through a private channel, never through the chat.\n\n## Phase 4 — Configure the selected connection inside the client\n\n| Client | Install |\n| --- | --- |\n| Any native OAuth client, including a supported Desktop\u002Fcloud connector | Enter the native registration's actual endpoint, client ID, callback, and method in its supported settings; confidential clients also receive the private OAuth secret. Start authorization in that client and obtain owner consent. Follow its `docs\u002Fmcp\u002F` guide when manual client configuration is unavailable; do not substitute machine credentials. |\n| Grok Bot using the private machine path | Inside the Bot: `gbrain connect https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp --harness grok-bot --credentials-file \u002Fprivate\u002Fagent-example.json --install --root \u002Fworkspace\u002Fgbrain`; enable the generated instruction as a native skill (a visible, separate step) |\n| Muse using the private machine path | Same `gbrain connect … --harness muse … --install --root \u003Cverified durable root>`; establish the durable user-files location first — never `\u002Ftmp`, never an invented path |\n| Claude Code \u002F Codex \u002F opencode using managed private configuration on another machine | `gbrain connect https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp --harness codex --credentials-file \u002Fprivate\u002Fagent-example.json --install`; select the actual adapter |\n| Local agents on the brain host | Postgres: `gbrain bootstrap harness --yes --port 3131`. PGLite: `bootstrap harness` refuses under the live serve unless `--token` is passed — mint BEFORE the service runs (`gbrain auth create local-agents --scopes read,write`, plus `--member \u003Cid>` on a brain that serves several people, run before `gbrain mcp expose` or while the service is briefly stopped) and pass `gbrain bootstrap harness --yes --port 3131 --token \u003Cvalue>`; or skip hooks and use the scoped `gbrain mcp grant … --url http:\u002F\u002F127.0.0.1:3131\u002Fmcp --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --credentials-out \u002Fprivate\u002F\u003Cname>.json` + `gbrain connect http:\u002F\u002F127.0.0.1:3131\u002Fmcp --harness \u003Cid> --credentials-file \u002Fprivate\u002F\u003Cname>.json --install` path (MCP wiring only) |\n| ChatGPT \u002F Perplexity \u002F other cloud OAuth clients | Requires explicit `--funnel` when the runtime is outside the tailnet; follow the native OAuth path and per-client page under `docs\u002Fmcp\u002F` with the published URL |\n\nTailnet-only endpoints are reachable only from devices signed in to the same\ntailnet — if a device cannot resolve `your-machine.your-tailnet.ts.net`,\ninstall Tailscale there and sign in; do not switch to `--funnel` for that.\n\n## Phase 5 — Verify\n\nFor the private machine path, from the client's environment:\n\n```bash\ngbrain mcp verify --client CLIENT_ID --harness grok-bot \\\n  --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --credentials-file \u002Fprivate\u002Fagent-example.json --json\n```\n\n`server_status: \"passed\"` proves the permitted server checks; writable grants\nalso exercise randomized write\u002Freadback and cleanup. Native activation remains\nseparate; exit 2 (`partial`) is not completed native verification.\n\nFor native OAuth, observe `gbrain:\u002F\u002Fcapabilities` and an allowed call through\nthat harness's authenticated MCP connection. Do not pass an OAuth setup export\nto the private-handoff verifier. For either path, observe calls in the intended\nharness and inspect the advertised schemas before a memory test. With an\nauthorized writable grant, use only a harmless synthetic fact\nwith provenance and explicit `visibility: \"world\"` within its source grant;\nretain its returned ID, verify recall in a new conversation, correct it, and\nwithdraw it. Private facts cannot be recalled or withdrawn through these remote\noperations; never change real private content to pass a test. With read-only\naccess, observe an authenticated read without adding write authority. Report\nregistration, setup delivery, server checks, native calls, and cleanup separately.\n\nHost-side check at any time: `gbrain mcp expose --status` (without a receipt\nit reports leftovers as `leftovers_without_receipt`, exit 1, with the exact\ncleanup command). Remove the exposure and host service:\n`gbrain mcp expose --remove` (stops and removes the service — also when the\nreceipt says it was skipped but the unit exists — clears only gbrain's\nserve\u002Ffunnel handler with `--https=443 --set-path=\u002F off`, keeps Tailscale\ninstalled and signed in — the Linux operator preference too — and keeps the\nadmin token unless `--force`; without a receipt — an interrupted publish — it\nrecovers from what is on disk: the wrapper, the unit and the `:443` handler\nfor `--port`, default 3131, leaving the token; the handler is turned off only\nwhen the wrapper, unit or service corroborates it — a handler standing alone\nis reported and left until `--force`). A handler that survives the `off` is\nnever reported as removed: exit 1 `handler_not_removed`, receipt + wrapper\nkept for the re-run. An unreadable `tailscale serve status` stops both paths\nwith exit 1 `tailscale_serve_status_unreadable` (the service is already\nremoved; receipt and\u002For wrapper kept) — fix Tailscale, re-run the printed\ncommand. Declining the prompt exits 2 with \"Nothing changed.\"; a\n`--no-service` re-run keeps an existing service.\n\nRemoving exposure does not revoke client registrations or erase brain data.\nUse the owner lifecycle commands in [MCP administration](https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Fmcp\u002FADMIN.md)\nto invalidate tokens, revoke, or delete a selected client after reviewing its\nconsequences and revision.\n\n## PGLite single-writer note\n\nWhile the exposed server runs, it holds the PGLite lock. Host-side commands\nthat open the database (`gbrain doctor`, `gbrain mcp grant` WITHOUT an owner\nfile or protected `GBRAIN_ADMIN_BOOTSTRAP_TOKEN`, `gbrain bootstrap harness`\nwithout `--token`) FAIL FAST\nwith `live_serve` — they do not wait. Administer through the running server\ninstead. `gbrain sync` and `gbrain sweep --once` are the exceptions: they\ndelegate into the live serve automatically. Always provision through\n`--admin-token-file`; if the user needs concurrent local commands, route to\n[postgres-adopt](..\u002Fpostgres-adopt\u002FSKILL.md) rather than stopping the server.\n\n## When it fails\n\nFollow the [agent operator protocol](..\u002Fconventions\u002Fagent-operator-protocol.md) for any gbrain error `code`, exit code, `[AGENT]` block or notice block. Specific to this skill:\n\n- `gbrain mcp expose --status` reports `not_exposed` (exit 2) or `pending`: wait for the certificate or service as the status says; `leftovers_without_receipt` (exit 1) means an interrupted run, so follow its `fix`.\n- Installing Tailscale or a service needs the user's agreement before `--yes`; `gbrain mcp expose` still exits 2 when it needs confirmation (documented legacy), so read the block, not just the exit code.\n- A client gets `invalid_token` \u002F `insufficient_scope`: issue a scoped grant for that client; never hand out an admin credential to make it connect.\n\n## Anti-Patterns\n\n- NEVER recommend ngrok or a cloud host first; Tailscale is the default and\n  the alternatives are for people who explicitly want them.\n- NEVER `gbrain serve --http --bind 0.0.0.0` for this shape. Tailscale Serve\n  terminates TLS and forwards to loopback; the default bind is correct.\n- NEVER pass `--funnel` silently. Say that it makes the endpoint publicly\n  reachable and why this client needs it.\n- NEVER paste the admin token or a credentials file into the conversation,\n  a commit, or a command argument the harness logs.\n- NEVER run `tailscale serve reset`, `tailscale logout`, or uninstall\n  Tailscale to clean up — `gbrain mcp expose --remove` touches only gbrain's\n  handler.\n- NEVER grant `operator`\u002F`full`\u002F`admin` because a health check failed.\n- NEVER treat OAuth `admin` scope as owner authority, or install a native\n  OAuth setup file as a private machine handoff.\n- NEVER claim the client is connected because `mcp verify` passed; native\n  activation and new-conversation recall are observed in the client.\n- NEVER open the live PGLite database from a second process to provision.\n\n## Output Format\n\nReport the host state and one line per client:\n\n```\nMCP URL   https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp   (reach: tailnet only | public via Funnel)\nService   launchd com.gbrain.serve, running   (log: ~\u002F.gbrain\u002Fserve\u002Fserve.log)\nEngine    pglite — provision via --admin-token-file\nClient    agent-example (grok-bot, memory-writer): granted, installed, verify passed, native activation pending\nPending   \u003Cexact next command, or \"nothing\">\n```\n\nQuote `gbrain mcp expose --status` and `gbrain mcp verify` output as-is (both\nare redacted). Name every unverified step explicitly; a passing server check\nnever stands in for observed recall inside the client.\n","# Remote MCP — use your brain from anywhere\n\n> The brain already runs on the user's computer. This skill makes it reachable\n> over MCP from their other devices and from the agents they use elsewhere,\n> without moving the data and without putting a database on the public\n> internet. Tailscale is the default transport; publishing is one command,\n> access is one scoped grant per client.\n\n## Contract\n\nThis skill guarantees:\n- **Tailscale by default.** `gbrain mcp expose` publishes the local server\n  with `tailscale serve` (HTTPS on the tailnet, nothing public). ngrok and\n  cloud hosts stay documented alternatives, never the first suggestion.\n- **Funnel is explicit.** Cloud agents whose runtime is not on the tailnet\n  (Grok Bot, Muse, ChatGPT, Claude.ai \u002F Cowork, Perplexity) need\n  `--funnel`, which makes the same `*.ts.net` name publicly reachable. Say\n  so before running it; the endpoint is then protected by gbrain's OAuth \u002F\n  bearer auth and scoped grants, not by the network.\n- **Consent before system changes.** Installing Tailscale, running\n  `tailscale up`, writing a serve\u002Ffunnel config and creating a user service\n  are host-state changes. Show the operator the printed plan and get a yes\n  before passing `--yes`.\n- **Engine-free.** `gbrain mcp expose` never opens the database. On a PGLite\n  brain the running server owns the single-writer lock, so every later\n  provisioning step goes through the server's authenticated admin API\n  (`--admin-token-file`), never a second process on the database.\n- **No secrets in chat.** The admin token lives in\n  the receipt's `admin_token_file` (normally `~\u002F.gbrain\u002Fserve\u002Fadmin-token`);\n  machine and confidential OAuth credentials land in a private\n  `--credentials-out` file. Public PKCE has no secret. Quote redacted\n  receipts; deliver a requested owner login link privately and unopened.\n- **Owner authority is separate.** The expose-managed wrapper loads its\n  token file as `GBRAIN_ADMIN_BOOTSTRAP_TOKEN`; that file authorizes\n  `gbrain mcp admin` and `mcp grant` against that server. An ordinary MCP\n  token, client secret, or OAuth `admin` scope does not. With `--no-service`,\n  use the independently running server's actual configured owner credential;\n  creating an expose token file does not configure that process.\n- **Least privilege.** One client per consumer, `memory-writer` for ordinary\n  memory use or `memory-reader` for read-only access. Broader access requires\n  the user's request. Never `operator` \u002F `full` \u002F `admin` to make a convenience\n  check pass.\n- **Server checks are not native activation.** `gbrain mcp verify` accepts a\n  private machine handoff and checks the access that grant permits. Native\n  OAuth is verified through the native client's authenticated connection.\n  A generated file or server probe does not prove that client loaded its tools.\n\n## Decision table — who is connecting?\n\n| Client | Shape | Command |\n| --- | --- | --- |\n| Your own devices: Claude Desktop, Claude Code \u002F Codex \u002F opencode on another laptop, phone apps joined to the tailnet | tailnet-only HTTPS (default) | `gbrain mcp expose --dry-run` (preview; the run itself is Phase 2, after consent) |\n| Cloud agents running in a vendor's cloud: Grok Bot, Muse, ChatGPT connector, Claude.ai \u002F Cowork, Perplexity Computer | public HTTPS on the same `*.ts.net` name | `gbrain mcp expose --funnel --dry-run` (preview; Phase 2 after consent) |\n| Cloud agent whose runtime you can join to your tailnet (userspace `tailscaled`, ephemeral auth key) | tailnet-only | Advanced, unverified, not automated — see the [remote MCP guide](https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Fguides\u002Fremote-mcp.md) |\n| Local agents on the same machine (Claude Code, Codex, opencode) | loopback — no Tailscale needed | Postgres: `gbrain bootstrap harness --yes --port 3131`. PGLite: mint the token BEFORE the service runs (`gbrain auth create local-agents --scopes read,write`, plus `--member \u003Cid>` on a brain that serves several people — before `gbrain mcp expose`, or while the service is briefly stopped) and pass `gbrain bootstrap harness --yes --port 3131 --token \u003Cvalue>`; OR use the scoped path `gbrain mcp grant \u003Cname> --harness \u003Cid> --profile memory-writer --source default --url http:\u002F\u002F127.0.0.1:3131\u002Fmcp --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --credentials-out \u002Fprivate\u002F\u003Cname>.json` then `gbrain connect http:\u002F\u002F127.0.0.1:3131\u002Fmcp --harness \u003Cid> --credentials-file \u002Fprivate\u002F\u003Cname>.json --install` (MCP wiring only, no per-turn hooks) |\n| Thin client only (this machine has no brain) | — | Stop: run this skill on the brain host |\n\nGrok Bot and Muse users with no always-on machine keep the in-agent local\ninstall described in [setup](..\u002Fsetup\u002FSKILL.md) as the alternative.\n\nNetwork reach and authentication are separate choices. A vendor name or\ntailnet\u002FFunnel mode does not select the credential flow: inspect the intended\nclient's native OAuth settings or supported private-handoff adapter in Phase 3.\n\n## Phase 1 — Detect\n\n```bash\ngbrain engine status --json\ngbrain mcp expose --status --json\n```\n\n- `thin_client: true` → the brain lives elsewhere. Stop and say the command\n  runs on the brain host.\n- `effective_engine: \"pglite\"` → note the single-writer rule (below) before\n  continuing; `\"postgres\"` → concurrent local commands stay fine.\n- `--status` reports `status: \"not_exposed\"` (exit 2 with `--json`) →\n  Phase 2. `status: \"exposed\"` → skip to Phase 3 with the printed\n  `mcp_url`. `status: \"pending\"` → the certificate or service is still coming\n  up; re-run `--status` in a minute before changing anything.\n  `reason: \"leftovers_without_receipt\"` (exit 1) → an interrupted run left\n  the wrapper, the unit \u002F service or a `:443` handler behind; run the exact\n  `gbrain mcp expose --remove --yes …` line from `next_actions` (it carries\n  `--force` only when a handler stands alone), then Phase 2.\n- The brain is already reachable over HTTPS by other means (an existing\n  `serve --http` behind ngrok \u002F a reverse proxy, or a brain hosted elsewhere) →\n  do NOT run `gbrain mcp expose`. Go straight to hosted access: choose the\n  client's connection method and register it against that endpoint (Phase 3\n  with its URL and owner authority), then configure the client (Phase 4).\n- Ask which clients should reach the brain (the decision table decides\n  tailnet vs `--funnel`). Do not ask questions the user already answered.\n\n## Phase 2 — Publish\n\nPreview first, then run with consent:\n\n```bash\ngbrain mcp expose --dry-run            # prints the plan; changes nothing\ngbrain mcp expose --yes                # your own devices (tailnet only)\ngbrain mcp expose --funnel --yes       # a cloud agent must reach it\n```\n\nOptions: `--port N` (default 3131), `--surface verbs|starter|full` (default\n`full`, mirrors `serve --http`), `--enable-dcr`, `--no-tailscale` (publish\nnothing; just install the service), `--no-service` (publish only; the\noperator runs `serve` themselves), `--no-install` (fail instead of\ninstalling Tailscale), `--force` (take over a serve handler that already\npoints at another local port), `--json`.\n\nWhat the command does, in order: plan (refuses up front when anything —\nHTTP or not — already listens on the port and `--no-service` is absent, and\nwhen `--no-tailscale` is passed over a receipt that records a live tailnet\nmapping) → consent → find or install the `tailscale` binary → sign in if\nneeded (Linux: `sudo tailscale set --operator=$USER`, then `sudo tailscale up`\n— `sudo` only in front of a system-installed binary; the operator preference\nis persistent and `--remove` does not revoke it; macOS: `tailscale up`) →\nread the machine's tailnet DNS name and pre-check that HTTPS certificates are\nenabled (and, with `--funnel`, that the node has the Funnel capability) →\n`tailscale serve` \u002F `funnel` → ensure the admin token →\nwrite the wrapper and a launchd agent \u002F systemd user unit → poll local and\ntailnet `\u002Fhealth` → write the receipt `~\u002F.gbrain\u002Fserve\u002Fexpose.json`. With\n`--json` every step is a named check (`plan`, `consent`, `tailscale.binary`,\n`tailscale.login`, `tailscale.identity`, `tailscale.publish`, `admin_token`,\n`service`, `verify.local`, `verify.tailnet`, `receipt`); the health checks\nare `verify.local` and `verify.tailnet`, not `verify`.\n\nRelay every prompt the command surfaces:\n\n| Output | What you do |\n| --- | --- |\n| Tailscale login URL printed by `tailscale up` | Give the user the URL, wait for them to sign in; on exit 2 (`tailscale_login_pending`) re-run the exact command the output prints |\n| `declined` (exit 2, \"Nothing changed.\") | The operator said no at the prompt. Nothing was installed or published; pass `--yes` only after they confirm the printed plan |\n| `tailscale_https_not_enabled` — pre-check, exit 2, nothing published (`CertDomains` empty) | Enable MagicDNS + HTTPS Certificates at `https:\u002F\u002Flogin.tailscale.com\u002Fadmin\u002Fdns`, then re-run the printed command. The same reason with exit 1 means the `tailscale serve` call itself refused for the same cause — same fix |\n| `tailscale_funnel_not_enabled` — pre-check, exit 2, nothing published (the node lacks the Funnel capability) | Enable the `funnel` node attribute in the tailnet policy (`https:\u002F\u002Flogin.tailscale.com\u002Fadmin\u002Facls`; see `https:\u002F\u002Ftailscale.com\u002Fkb\u002F1223\u002Ffunnel`), then re-run. Exit 1 with this reason is the post-`funnel` classified form — same fix |\n| `tailscale_needs_operator` | Run `sudo tailscale set --operator=$USER`, then re-run |\n| `tailscale_unknown` (exit 1) — `tailscale serve --bg` did not finish within 60s | The CLI is waiting on the operator (an enablement step the pre-checks could not see). Have them run the printed `tailscale serve --bg \u003Cport>` by hand to see its prompt, then re-run |\n| `tailscale_needs_login` | Linux: `sudo tailscale set --operator=$USER` then `sudo tailscale up`; macOS: `tailscale up` or open the Tailscale app and sign in; then re-run |\n| `tailscale_login_manual` (exit 2) — \"tailscale at \u003Cpath> is not a system install, so gbrain will not run it with sudo\" | The `tailscale` on `PATH` is user-writable (e.g. `~\u002F.local\u002Fbin`); gbrain never runs it as root. The printed command names that path — `sudo \u003Cpath> set --operator=$USER && sudo \u003Cpath> up` (a bare `sudo tailscale` would not find a binary outside sudo's `secure_path`). Running it is the operator choosing to trust that binary: say so, let them run it themselves, then re-run the printed command |\n| `tailscale_receipt_present` (exit 1, `plan` fails) — \"This brain is already published on your tailnet at \u003Curl>\" | `--no-tailscale` was passed while a live Serve\u002FFunnel mapping is on record. Run `gbrain mcp expose --remove --yes` first, or drop `--no-tailscale` |\n| `tailscale_daemon_not_running` (exit 2) | macOS `open -a Tailscale`; Linux `sudo systemctl enable --now tailscaled`; then re-run |\n| `foreign_serve_config` — \"tailscale serve already proxies :443 to …\" (exit 1) | Show the user what the existing handler proxies (`tailscale serve status`); only `--force` on their explicit yes. A handler owned by another terminal's foreground `tailscale serve` session cannot be taken over — it must be stopped in that terminal |\n| `foreign_listener` — \"Something already answers on 127.0.0.1:\u003Cport> and no expose receipt claims it\" (exit 1, refused BEFORE anything is published; ANY listener counts — an accepted TCP connect from a non-HTTP service as much as a 404) | Stop the other process, pick another `--port`, or pass `--no-service` to publish it as-is. If it is a gbrain server left by an interrupted run, `gbrain mcp expose --remove --yes` first (receipt-less recovery) |\n| `no_brain_config` (exit 1, `plan` fails) — \"No brain is configured on this host (gbrain init first), so a service would only crash-loop\" | Run `gbrain init` on this host first, then re-run; or pass `--no-service` to only publish a server the operator starts themselves. Never install the service around a missing brain |\n| `pglite_lock` warn — \"a live process holds this PGLite brain (pid N, …)\" | Tell the user which process holds the lock; the service cannot start until it exits. Stop it (or route to [postgres-adopt](..\u002Fpostgres-adopt\u002FSKILL.md) for concurrent use), then `gbrain mcp expose --status` |\n| \"could not read tailscale serve status\" — `tailscale.publish` fails (publish: exit 1 `tailscale_\u003Ckind>`, nothing published; `--status`: exit 1; `--remove`: exit 1 `tailscale_serve_status_unreadable` — with a receipt, receipt + wrapper kept; without one, the service is still removed but the wrapper is kept as the corroboration the re-run needs) | The command fails closed instead of guessing. Run `tailscale serve status --json`, apply the classified fix (operator \u002F daemon \u002F login), then re-run the same command |\n| `handler_not_removed` (`--remove`, exit 1) — \"the tailscale handler for port \u003Cport> is still present\" \u002F \"could not be confirmed gone\" | The `off` ran but gbrain's handler survived (or the re-read failed). The service is already gone; the receipt and wrapper were kept on purpose. Run `tailscale serve status`, fix what it reports, then `gbrain mcp expose --remove --yes` again — never `serve reset` |\n| `verify.tailnet: warn` — \"this host cannot resolve \u003Cname>\" (exit 0) | MagicDNS is off on the brain host itself; `tailscale set --accept-dns=true`, then `gbrain mcp expose --status`. Other devices may already reach the server; do not treat it as a certificate wait |\n| `service: manual` (no supervisor: cloud sandbox, container) | Relay the printed foreground and `nohup … &` commands; this is a documented outcome, not a failure |\n| `verify.local: warn` (exit 2, `local_health_timeout`) | The handler is published, the service installed and the receipt written — this exit 2 is NOT \"nothing published\"; `--status` \u002F `--remove` find them. `\u002Fhealth` did not answer within the wait: check `~\u002F.gbrain\u002Fserve\u002Fserve.err`, then `gbrain mcp expose --status` |\n| `verify.tailnet: pending` (exit 2, `tailnet_health_pending`; `--status` reports it with exit 1) | Same: handler, service and receipt are already in place. First certificate issuance can take a minute; `gbrain mcp expose --status` later |\n| `confirmation_required` (exit 2) | Non-TTY run without `--yes`. Nothing changed; show the printed plan, relay the payload's `user_message`, and run its `fix.command` (it adds `--yes`) only after the operator agrees |\n| `tailscale_no_dns_name` (exit 1) — the node has no MagicDNS name | Enable MagicDNS + HTTPS Certificates at `https:\u002F\u002Flogin.tailscale.com\u002Fadmin\u002Fdns`, then re-run; nothing was published |\n| `tailscale_missing` (exit 1, `--no-install`) \u002F `tailscale_unsupported_platform` \u002F `tailscale_install_failed` (exit 1) | Tailscale is not installed and was not (or could not be) installed. Relay the printed install command or `https:\u002F\u002Ftailscale.com\u002Fdownload`, have the user sign in, then re-run |\n| `tailscale_publish_unconfirmed` (exit 1) — `serve --bg` exited 0 but the re-read shows no handler (or could not be read) | Run `tailscale serve status`; `gbrain mcp expose --remove --yes` clears a handler for the port without a receipt (`--force` when no wrapper or service of gbrain's exists yet), then re-run |\n| `service_install_failed` (exit 1) | The handler IS published and the receipt written with `service.state: stopped`. Read the error and `~\u002F.gbrain\u002Fserve\u002Fserve.err`, fix, re-run; or `gbrain mcp expose --remove --yes` |\n| `status_unhealthy` (`--status`, exit 1) | A named check failed (`tailscale.publish`, `service`, `verify.local`); apply that row, then `gbrain mcp expose --yes` repairs |\n| `recovered_without_receipt` (`--remove`, exit 0) | The receipt-less recovery finished: report what was removed and what was left (the admin token stays) |\n\nNever work around a classified error by editing Tailscale state by hand;\napply the printed fix and re-run.\n\n## Phase 3 — Choose the client flow and owner authority\n\nUse the successful expose receipt's `mcp_url` and `admin_token_file`; substitute\nthem for the example URL\u002Fdefault path below, including when `GBRAIN_HOME` is\ncustomized. Run owner commands on the host or in a separately authorized admin\nharness with that server's protected credential. An ordinary connecting client\nhands these steps to that administrator; its OAuth scopes cannot grant owner\nauthority. Follow [mcp-access](..\u002Fmcp-access\u002FSKILL.md) and\n[MCP administration](https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Fmcp\u002FADMIN.md) for owner actions and recovery.\n\nTo open the dashboard:\n\n```bash\ngbrain mcp admin login-link --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --json\n```\n\nDeliver the single-use link privately without fetching it. The plain admin URL\ndoes not authenticate. For pending native consent, add `--oauth-request ID`\nwith the exact opaque ID from the client's browser request; the owner then\nreviews consent separately. Expired requests or a restart require restarting\nauthorization in the native client.\n\n| Intended connection | Register | Deliver |\n| --- | --- | --- |\n| Native public PKCE | `mcp admin register`, method `none`, exact client callback | Native OAuth metadata; no client secret |\n| Native confidential PKCE | `mcp admin register`, actual `client_secret_post` or `client_secret_basic`, exact callback | Private `OAuthClientSetup` via `--credentials-out` |\n| Machine credentials \u002F managed bearer configuration | `mcp grant --harness ID` | Private machine handoff for `gbrain connect` |\n\nFor native OAuth, obtain the actual redirect URI and authentication method from\nthe intended harness. `CLIENT_REDIRECT_URI` below must be replaced with that\nexact URI; it is not a suggested callback:\n\n```bash\ngbrain mcp admin register native-example --redirect-uri 'CLIENT_REDIRECT_URI' \\\n  --token-endpoint-auth-method none --profile memory-writer --source default \\\n  --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --dry-run --json\n```\n\nReview the preview, then repeat without `--dry-run`. For confidential PKCE, use\nthe client's actual POST\u002FBasic method and add `--credentials-out PRIVATE_FILE`\nbefore creation. Native registration creates authorization-code and refresh\ngrants, not a machine token. Retrieve instructions using its returned ID:\n\n```bash\ngbrain mcp admin setup CLIENT_ID --harness generic --flow authorization-code \\\n  --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --json\n```\n\nUse the actual supported adapter ID when known; `generic` gives manual native\nsettings instructions. The native client initiates OAuth and owns its verifier.\nAn `OAuthClientSetup` is not a machine handoff for `gbrain connect` or\n`gbrain mcp verify`. Keep DCR off unless the owner explicitly chooses it; manual\nowner registration works without `--enable-dcr`.\n\nFor a client using a private machine handoff, preview through the same running\nserver's admin API:\n\n```bash\ngbrain mcp grant agent-example --harness grok-bot --profile memory-writer \\\n  --source default \\\n  --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token \\\n  --credentials-out \u002Fprivate\u002Fagent-example.json --dry-run --json\n```\n\n- `--harness` is the real adapter id (`gbrain mcp adapters`): `grok-bot`,\n  `muse`, `claude-desktop`, `codex`, `claude-code`, `opencode`, ...\n- Review the preview, then repeat without `--dry-run` to create the client.\n- Profiles (`memory-reader`, `memory-writer`, `coding-agent`, `operator`,\n  `delegating-agent`, `full`) and delegation limits are defined in\n  [hosted harness access](https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Fguides\u002Fhosted-harness-access.md);\n  default to `memory-writer`, or `memory-reader` when only reading is requested.\n- The receipt is redacted; the credentials file is 0600. Move it to the\n  client through a private channel, never through the chat.\n\n## Phase 4 — Configure the selected connection inside the client\n\n| Client | Install |\n| --- | --- |\n| Any native OAuth client, including a supported Desktop\u002Fcloud connector | Enter the native registration's actual endpoint, client ID, callback, and method in its supported settings; confidential clients also receive the private OAuth secret. Start authorization in that client and obtain owner consent. Follow its `docs\u002Fmcp\u002F` guide when manual client configuration is unavailable; do not substitute machine credentials. |\n| Grok Bot using the private machine path | Inside the Bot: `gbrain connect https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp --harness grok-bot --credentials-file \u002Fprivate\u002Fagent-example.json --install --root \u002Fworkspace\u002Fgbrain`; enable the generated instruction as a native skill (a visible, separate step) |\n| Muse using the private machine path | Same `gbrain connect … --harness muse … --install --root \u003Cverified durable root>`; establish the durable user-files location first — never `\u002Ftmp`, never an invented path |\n| Claude Code \u002F Codex \u002F opencode using managed private configuration on another machine | `gbrain connect https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp --harness codex --credentials-file \u002Fprivate\u002Fagent-example.json --install`; select the actual adapter |\n| Local agents on the brain host | Postgres: `gbrain bootstrap harness --yes --port 3131`. PGLite: `bootstrap harness` refuses under the live serve unless `--token` is passed — mint BEFORE the service runs (`gbrain auth create local-agents --scopes read,write`, plus `--member \u003Cid>` on a brain that serves several people, run before `gbrain mcp expose` or while the service is briefly stopped) and pass `gbrain bootstrap harness --yes --port 3131 --token \u003Cvalue>`; or skip hooks and use the scoped `gbrain mcp grant … --url http:\u002F\u002F127.0.0.1:3131\u002Fmcp --admin-token-file ~\u002F.gbrain\u002Fserve\u002Fadmin-token --credentials-out \u002Fprivate\u002F\u003Cname>.json` + `gbrain connect http:\u002F\u002F127.0.0.1:3131\u002Fmcp --harness \u003Cid> --credentials-file \u002Fprivate\u002F\u003Cname>.json --install` path (MCP wiring only) |\n| ChatGPT \u002F Perplexity \u002F other cloud OAuth clients | Requires explicit `--funnel` when the runtime is outside the tailnet; follow the native OAuth path and per-client page under `docs\u002Fmcp\u002F` with the published URL |\n\nTailnet-only endpoints are reachable only from devices signed in to the same\ntailnet — if a device cannot resolve `your-machine.your-tailnet.ts.net`,\ninstall Tailscale there and sign in; do not switch to `--funnel` for that.\n\n## Phase 5 — Verify\n\nFor the private machine path, from the client's environment:\n\n```bash\ngbrain mcp verify --client CLIENT_ID --harness grok-bot \\\n  --url https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp \\\n  --credentials-file \u002Fprivate\u002Fagent-example.json --json\n```\n\n`server_status: \"passed\"` proves the permitted server checks; writable grants\nalso exercise randomized write\u002Freadback and cleanup. Native activation remains\nseparate; exit 2 (`partial`) is not completed native verification.\n\nFor native OAuth, observe `gbrain:\u002F\u002Fcapabilities` and an allowed call through\nthat harness's authenticated MCP connection. Do not pass an OAuth setup export\nto the private-handoff verifier. For either path, observe calls in the intended\nharness and inspect the advertised schemas before a memory test. With an\nauthorized writable grant, use only a harmless synthetic fact\nwith provenance and explicit `visibility: \"world\"` within its source grant;\nretain its returned ID, verify recall in a new conversation, correct it, and\nwithdraw it. Private facts cannot be recalled or withdrawn through these remote\noperations; never change real private content to pass a test. With read-only\naccess, observe an authenticated read without adding write authority. Report\nregistration, setup delivery, server checks, native calls, and cleanup separately.\n\nHost-side check at any time: `gbrain mcp expose --status` (without a receipt\nit reports leftovers as `leftovers_without_receipt`, exit 1, with the exact\ncleanup command). Remove the exposure and host service:\n`gbrain mcp expose --remove` (stops and removes the service — also when the\nreceipt says it was skipped but the unit exists — clears only gbrain's\nserve\u002Ffunnel handler with `--https=443 --set-path=\u002F off`, keeps Tailscale\ninstalled and signed in — the Linux operator preference too — and keeps the\nadmin token unless `--force`; without a receipt — an interrupted publish — it\nrecovers from what is on disk: the wrapper, the unit and the `:443` handler\nfor `--port`, default 3131, leaving the token; the handler is turned off only\nwhen the wrapper, unit or service corroborates it — a handler standing alone\nis reported and left until `--force`). A handler that survives the `off` is\nnever reported as removed: exit 1 `handler_not_removed`, receipt + wrapper\nkept for the re-run. An unreadable `tailscale serve status` stops both paths\nwith exit 1 `tailscale_serve_status_unreadable` (the service is already\nremoved; receipt and\u002For wrapper kept) — fix Tailscale, re-run the printed\ncommand. Declining the prompt exits 2 with \"Nothing changed.\"; a\n`--no-service` re-run keeps an existing service.\n\nRemoving exposure does not revoke client registrations or erase brain data.\nUse the owner lifecycle commands in [MCP administration](https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Fmcp\u002FADMIN.md)\nto invalidate tokens, revoke, or delete a selected client after reviewing its\nconsequences and revision.\n\n## PGLite single-writer note\n\nWhile the exposed server runs, it holds the PGLite lock. Host-side commands\nthat open the database (`gbrain doctor`, `gbrain mcp grant` WITHOUT an owner\nfile or protected `GBRAIN_ADMIN_BOOTSTRAP_TOKEN`, `gbrain bootstrap harness`\nwithout `--token`) FAIL FAST\nwith `live_serve` — they do not wait. Administer through the running server\ninstead. `gbrain sync` and `gbrain sweep --once` are the exceptions: they\ndelegate into the live serve automatically. Always provision through\n`--admin-token-file`; if the user needs concurrent local commands, route to\n[postgres-adopt](..\u002Fpostgres-adopt\u002FSKILL.md) rather than stopping the server.\n\n## When it fails\n\nFollow the [agent operator protocol](..\u002Fconventions\u002Fagent-operator-protocol.md) for any gbrain error `code`, exit code, `[AGENT]` block or notice block. Specific to this skill:\n\n- `gbrain mcp expose --status` reports `not_exposed` (exit 2) or `pending`: wait for the certificate or service as the status says; `leftovers_without_receipt` (exit 1) means an interrupted run, so follow its `fix`.\n- Installing Tailscale or a service needs the user's agreement before `--yes`; `gbrain mcp expose` still exits 2 when it needs confirmation (documented legacy), so read the block, not just the exit code.\n- A client gets `invalid_token` \u002F `insufficient_scope`: issue a scoped grant for that client; never hand out an admin credential to make it connect.\n\n## Anti-Patterns\n\n- NEVER recommend ngrok or a cloud host first; Tailscale is the default and\n  the alternatives are for people who explicitly want them.\n- NEVER `gbrain serve --http --bind 0.0.0.0` for this shape. Tailscale Serve\n  terminates TLS and forwards to loopback; the default bind is correct.\n- NEVER pass `--funnel` silently. Say that it makes the endpoint publicly\n  reachable and why this client needs it.\n- NEVER paste the admin token or a credentials file into the conversation,\n  a commit, or a command argument the harness logs.\n- NEVER run `tailscale serve reset`, `tailscale logout`, or uninstall\n  Tailscale to clean up — `gbrain mcp expose --remove` touches only gbrain's\n  handler.\n- NEVER grant `operator`\u002F`full`\u002F`admin` because a health check failed.\n- NEVER treat OAuth `admin` scope as owner authority, or install a native\n  OAuth setup file as a private machine handoff.\n- NEVER claim the client is connected because `mcp verify` passed; native\n  activation and new-conversation recall are observed in the client.\n- NEVER open the live PGLite database from a second process to provision.\n\n## Output Format\n\nReport the host state and one line per client:\n\n```\nMCP URL   https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp   (reach: tailnet only | public via Funnel)\nService   launchd com.gbrain.serve, running   (log: ~\u002F.gbrain\u002Fserve\u002Fserve.log)\nEngine    pglite — provision via --admin-token-file\nClient    agent-example (grok-bot, memory-writer): granted, installed, verify passed, native activation pending\nPending   \u003Cexact next command, or \"nothing\">\n```\n\nQuote `gbrain mcp expose --status` and `gbrain mcp verify` output as-is (both\nare redacted). Name every unverified step explicitly; a passing server check\nnever stands in for observed recall inside the client.\n",{"name":5,"tools":50,"mutating":52,"triggers":53,"brain_first":65,"description":66,"when_to_use":67},[51],"exec",true,[54,55,56,57,58,59,60,61,62,63,64],"use my brain over mcp","serve my brain over mcp","expose my brain over mcp","gbrain mcp server","remote mcp access to my brain","put my brain on tailscale","connect grok bot to my brain","connect muse to my brain","connect claude desktop to my brain","reach my brain from my phone","gbrain mcp expose","exempt","Publish the user's own gbrain over MCP so other devices, desktop apps and\ncloud agents can reach it. `gbrain mcp expose` installs and signs in\nTailscale, publishes the running `gbrain serve --http` on the tailnet\n(HTTPS, tailnet-only by default; Funnel only when a client lives in a\nvendor cloud), keeps the server alive as a user service, and hands back the\nMCP URL. Then register one least-privilege client per consumer, choose\nnative OAuth\u002FPKCE or a private machine handoff for that client, and verify\nthe connection inside the intended harness.\n","Use when the user asks: \"use my brain over mcp\", \"serve my brain over mcp\", \"expose my brain over mcp\", \"gbrain mcp server\", \"remote mcp access to my brain\".","master","1e2fdc28a560c4a80a6e1f2d164d598032de41ff","plugin\u002Fskills\u002Fremote-mcp",4902,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain","https:\u002F\u002Fgithub.com\u002Fgarrytan","https:\u002F\u002Fblog.garrytan.com","Writes software, dreams. ","2026-11-14 04:11:30",{"source":8,"skillName":5,"commands":78,"prompt":88,"zipAvailable":52},[79,82,85],{"tool":80,"command":81},"npx","npx skills add garrytan\u002Fgbrain --skill remote-mcp",{"tool":83,"command":84},"bunx","bunx skills add garrytan\u002Fgbrain --skill remote-mcp",{"tool":86,"command":87},"pnpm","pnpm dlx skills add garrytan\u002Fgbrain --skill remote-mcp","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fplugin\u002Fskills\u002Fremote-mcp\u002FSKILL.md",[90],{"path":91,"resourceType":92,"resourceTypeDesc":93,"sizeBytes":94,"sourceHtmlUrl":95},"routing-eval.jsonl","OTHER","其他",1292,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fplugin\u002Fskills\u002Fremote-mcp\u002Frouting-eval.jsonl",[97,110,116,138,165,179],{"skillNo":98,"name":99,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":102,"publishedAt":45},"01M6N109FNGH1PK49T734PZXGB","query-helper","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog\u002Fskills\u002Fquery-helper\u002FSKILL.md",[],[103,104,105,106,107,108,109],"query helper","helper","query","search","page","find","false",{"skillNo":111,"name":112,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":115,"publishedAt":45},"01M6N108A23GW6NGQEQ0E7GV0A","broken","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog\u002Fskills\u002Fbroken\u002FSKILL.md",[],[112],{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":122,"publishedAt":45},"01M6N105E88C5K2H03H0CC79G1","brain-ops","Read, enrich, and write brain pages with source attribution.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog\u002Fskills\u002Fbrain-ops\u002FSKILL.md",[],[123,28,124,125,126,107,127,128,129,130,105,131,132,133,134,135,136,137,106],"brain ops","ops","true","attribution","read","write","up","look","pages","people","with","enrich","web","path","source",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":149,"publishedAt":45},"01M6N102GRMF22TVCNT6RK5MRD","meeting-prep","Build a briefing before you meet someone, from their recent threads, open loops and what they care about.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog-query\u002Fskills\u002Fmeeting-prep\u002FSKILL.md",[144],{"categoryNo":145,"categoryName":146,"taxonomyType":147,"taxonomyTypeDesc":148},"01JBSEEDCAT000000000000034","行政与办公人员","PROFESSION","适用职业",[150,151,152,153,154,106,155,156,157,105,109,158,159,160,161,162,163,164],"meeting","prep","meeting prep","me","prepare","brief","open","build","loops","about","you","briefing","threads","for","Meet",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":171,"publishedAt":45},"01M6N1005QK7REHX5KGMNR5PF0","inbox-zero","Triage the inbox into replies, delegations and archives.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog-query\u002Fskills\u002Finbox-zero\u002FSKILL.md",[],[172,173,174,106,175,109,176,177,178],"inbox zero","ZeRO","inbox","clear","replies","my","triage",{"skillNo":180,"name":181,"nameZh":6,"description":182,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":183,"categories":184,"keywords":185,"publishedAt":45},"01M6N0ZXQ8RRRW71PNV61T57GP","deal-memo","Draft an investment memo from the deal pages and the founder history already in the brain.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog-query\u002Fskills\u002Fdeal-memo\u002FSKILL.md",[],[186,187,188,131,28,189,190,128,191,125,106,192],"deal memo","deal","memo","draft","investment","history","founder"]