[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-94620":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":15,"starSnapshotCount":15,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},94620,"aki-mcp-sv","lacvietanh\u002Faki-mcp-sv","lacvietanh","Custom MCP [Connector] for AI chat web [Claude|ChatGPT|Grok...] use Files\u002FShell on your computer directly","https:\u002F\u002Fakitao.com\u002Fpj\u002Faki-mcp-sv\u002F",null,"JavaScript",108,27,1,0,7,45.04,"MIT License",false,"main",true,[],"2026-08-24 04:01:22","# aki-mcp-sv\n\nGive Claude on the **web** (claude.ai), **ChatGPT**, and **Grok** read\u002Fedit access to files and a whitelisted shell on your local machine. Operates over HTTPS through a swappable public edge (Tailscale Funnel by default, or your own Cloudflare tunnel \u002F any stable HTTPS edge), gated by OAuth 2.1. *(Experimental support for Gemini — see [Connecting from Grok and Gemini](#connecting-from-grok-and-gemini).)*\n\nNo desktop app. No device lock-in. No install needed if you use the standalone launcher below.\n\n\u003Cimg width=\"1190\" height=\"1062\" alt=\"aki-mcp-sv control panel\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F760a7202-ad61-4f5d-86e3-973e90c74bd3\" \u002F>\n\n[![Version](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fversion-1.9.3-blue.svg)](CHANGELOG.md) [![License: MIT](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLicense-MIT-yellow.svg)](https:\u002F\u002Fopensource.org\u002Flicenses\u002FMIT) [![Platform](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fplatform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)](#install)\n\n**Contents:** [Why this exists](#why-this-exists) · [When to use & Core Use-Cases](#when-to-use--core-use-cases) · [Install](#install) · [Run](#run) · [Connecting from Claude web](#connecting-from-claude-web) · [Connecting from ChatGPT](#connecting-from-chatgpt) · [Connecting from Grok and Gemini](#connecting-from-grok-and-gemini) · [Autonomous Cloud Automation](#autonomous-cloud-automation-grok--local-mcp) · [Requirements](#requirements) · [Architecture](#architecture) · [Directory layout](#directory-layout) · [Configuration](#configuration) · [Exposing to the internet](#exposing-to-the-internet) · [Finding files](#finding-files) · [Security](#security)\n\n## Why this exists\n\nClaude.ai's web\u002FPro quota is far cheaper than paying per token via the API for equivalent usage. But most real work is project work: reading, editing, and running commands against files on your machine, not open-ended chat.\n\nThe Claude Desktop app already does local file access, but ties usage to a device ID you don't control, and running multiple accounts means repeated login\u002Flogout. With this web-based approach, you get true multi-account flexibility instead: just switch browser profiles to pick up a different account (e.g. several Claude Pro subscriptions), all pointed at the same local machine, no device lock-in.\n\n**aki-mcp-sv** routes around both problems: run an MCP server on your machine, expose it over HTTPS through Tailscale Funnel, and connect it to claude.ai as a custom connector.\n\n**The payoff:**\n- **Use your web quota** for local file and shell access, straight from the browser.\n- **True multi-account flexibility:** switch browser profiles to instantly pick up a different account, all pointed at the same machine.\n- **Safe by default:** a strict command whitelist, not a leaky blocklist — see [Security](#security).\n\n### When to use & Core Use-Cases\n\n- **At your desk:** a native Terminal\u002FCLI (Claude Code, Antigravity CLI, Cursor) is still the fastest, most fluid option — use that.\n- **Away from your desk (mobile \u002F web \u002F a machine that isn't yours):** use `aki-mcp-sv` via Claude Web, ChatGPT Mobile, or Grok to check on a running job, read logs, clean up temp files, or pull the latest code on your home\u002Foffice machine.\n- **On a schedule, with nobody watching:** pair Grok's scheduled prompts with `aki-mcp-sv` for cloud-triggered local execution — see [Autonomous Cloud Automation](#autonomous-cloud-automation-grok--local-mcp).\n\n## Install\n\n> [!NOTE]\n> **Is this safe to run?** The standalone launchers extract a private Node runtime and the app payload strictly into your OS's per-user app-data directory (`~\u002FLibrary\u002FApplication Support\u002Faki-mcp-sv` on macOS, `%LOCALAPPDATA%\\aki-mcp-sv` on Windows, `${XDG_DATA_HOME:-~\u002F.local\u002Fshare}\u002Faki-mcp-sv` on Linux); your own settings\u002Ftokens live separately at `~\u002F.aki\u002Fmcpsv\u002F`. Nothing is installed system-wide, no background service or daemon is created, and no `sudo`\u002Fadministrator privileges are required. The shell tool is read-only by default (see [Security](#security)). Closing the terminal window stops the server completely.\n\n### Option 1: Standalone package (recommended — no Node.js needed)\n\nDownload the launcher for your OS from the [latest release](https:\u002F\u002Fgithub.com\u002Flacvietanh\u002Faki-mcp-sv\u002Freleases\u002Flatest) — **not** the green \"Code\" button's \"Download ZIP\" above, which is just the source and won't run:\n\n- **macOS**: double-click `aki-mcp-sv-\u003Cversion>-macos.command` (or run it from Terminal)\n- **Linux**: `chmod +x aki-mcp-sv-\u003Cversion>-linux.run && .\u002Faki-mcp-sv-\u003Cversion>-linux.run` (downloaded files aren't executable by default)\n- **Windows**: double-click `aki-mcp-sv-\u003Cversion>-windows.cmd` — still needs [Git for Windows](https:\u002F\u002Fgit-scm.com\u002Fdownload\u002Fwin) (or WSL) on `PATH`, see [Requirements](#requirements)\n\n**Handling first-run OS security warnings** — expected on an uncode-signed launcher, not a sign anything's wrong:\n- **Browser download warning** (Chrome\u002FEdge\u002FSafari flagging `.command`\u002F`.cmd`\u002F`.run` as an uncommon file type): click \"Keep\"\u002F\"Download anyway\".\n- **macOS Gatekeeper** (\"cannot be opened because the developer cannot be verified\"): right-click the `.command` file → Open once to bypass. If that option is missing (macOS 15+ dropped it), open **System Settings → Privacy & Security**, scroll down, and click **Open Anyway** — or run `xattr -d com.apple.quarantine \u003Cpath-to-file>` in Terminal first, which works on every macOS version.\n- **Windows SmartScreen** (\"Windows protected your PC\"): click **More info**, then **Run anyway**.\n\n**Operational notes:**\n- **First run** downloads and checksum-verifies the Node runtime + app payload; later runs reuse what's already downloaded, so they start fast with no network access needed.\n- **To start it again later** (after a reboot or closing the terminal): run the exact same launcher file again — it's still in your Downloads folder.\n- **Keep the terminal\u002Fconsole window open** — it's the running server, not just a progress log. Closing it stops everything, including the control panel and any active connection.\n\n### Option 2: Install from source (needs Node.js)\n\n```bash\ngit clone \u003Crepo-url> aki-mcp-sv\ncd aki-mcp-sv\nnpm install\n```\n\nThen see [Run](#run) below.\n\n## Run\n\n**Standalone package:** the launcher already started the server for you — no command to type. Everything below (what gets printed, what the control panel shows, the default folder access) still applies to you, so skim it before jumping to [Connecting from Claude web](#connecting-from-claude-web).\n\n**Git-clone path:**\n\n```bash\ncp .env.example .env   # optional: only if you need PUBLIC_ORIGIN or another non-default var\nnpm start\n```\n\nNothing needs preparing beforehand; `npm start` handles it:\n- **Passphrase** and **OAuth client ID\u002Fsecret** in `~\u002F.aki\u002Fmcpsv\u002F`: generated once, reused on every later run.\n- **Funnel**: checks `tailscale funnel status`; if port `9999` isn't on yet, runs `tailscale funnel --bg 9999` (idempotent: never toggles an already-enabled port).\n- Prints the 4 values you need: **Remote MCP server URL**, **OAuth Client ID**, **OAuth Client Secret** (paste into claude.ai), and **Passphrase** (enter on the confirmation page when you hit Connect).\n- Opens the **control panel** at `http:\u002F\u002F127.0.0.1:9998\u002F?t=\u003Ctoken>`. A step header maps the flow (0 Setup · 1 Connectors · 2 Install rules · 3 Instructions · 4 Extension), then the sections follow it: 0 Setup (a 3-tab ingress picker: Tailscale + Funnel \u002F Owned public origin \u002F Hosted domain), 1 Connectors, 2 Install akidevrule, 3 Instructions prompt, 4 Browser utilities, 5 allowed Folders, 6 shell allowlist.\n\nThe default allowed root is your **home directory** (`$HOME`, or `%USERPROFILE%` on Windows): the one folder guaranteed to exist on any machine and to hold the projects you actually want Claude to reach. In plain terms, that means the whole home folder (Desktop, Documents, Downloads, Photos, everything under it), not just the projects you meant to share. Add\u002Fremove folders from **panel section 5**: click \"+ Add folder…\" and type an absolute path (`\u002FUsers\u002Fyou\u002Fprojects` or `C:\\Users\\you\\projects`). Saving takes effect immediately for the shell, find, and search tools, no restart; the file read\u002Fwrite\u002Fedit tools run in a separate child process that only picks up the change after pressing \"Apply to file tools\" (or a full restart). To change the root from the start: `MCP_DATA_DIR=\u002Fother\u002Fpath npm start` (or `set MCP_DATA_DIR=D:\\work` then `npm start` on Windows cmd).\n\nBeyond `$MCP_DATA_DIR`, the filesystem server is also granted `~\u002F.aki` (where akidevrule deploys) and `~\u002F.claude`, so claude.ai can read your **native** `CLAUDE.md` and skill router the same way Claude Code does, with no copying or staging.\n\n`~\u002F.claude` is granted at the folder level (the filesystem server can't scope to individual files), so `.claude.json`\u002F`auth-cache.json` (session tokens) and `history.jsonl` (chat history) inside it are also reachable through the connector. This row is locked in panel section 5, with no delete button by design so it can't be revoked by accident; the panel itself cannot remove it. If you don't want `~\u002F.claude` granted at all, edit `~\u002F.aki\u002Fmcpsv\u002Fmcp-hub.config.json` and remove the `${HOME}\u002F.claude` entries from it before connecting, then restart the hub; claude.ai then loses access to your `CLAUDE.md` too.\n\n`npm start` runs in the foreground: Ctrl+C to stop, restart manually when needed. **After editing code, Ctrl+C and `npm start` again** (Node doesn't hot-reload).\n\n## Connecting from Claude web\n\n1. Go to **claude.ai → Settings → Connectors → Add custom connector**\n2. **Remote MCP server URL**: paste `https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp` (printed by `npm start`)\n3. **Advanced settings → OAuth Client ID \u002F OAuth Client Secret**: paste the two values `npm start` printed\n4. Click **Connect**: a local confirmation page opens; enter the **passphrase** shown in the control panel (section 1 · Connectors) to approve — or read it straight from `~\u002F.aki\u002Fmcpsv\u002Fpassphrase.txt`\n\nWhy not token-in-URL: `docs\u002Fref\u002Fclaude-connector.md`, `docs\u002Fresearch\u002Fclaude-ai-oauth-connector.md`.\n\nclaude.ai connects and calls the tool suite: `filesystem__*` plus the in-house `local__*` tools (`local__find_path`, `local__search_content`, `local__run_cmd`, `local__agy_run`, `local__kiro_read`).\n\n**Note on the connector icon:** claude.ai doesn't read the icon from the MCP server. It queries Google's favicon service with the tailnet's **apex domain**, not your host: `https:\u002F\u002Ft2.gstatic.com\u002FfaviconV2?...&url=http:\u002F\u002F\u003Ctailnet>.ts.net&size=32`. `\u003Ctailnet>.ts.net` has no public DNS record, so Google returns 404 and claude.ai falls back to a default letter icon. This server serves `\u002Ffavicon.ico` publicly, but no file placed here can change that result: your subdomain never appears in the query Google receives.\n\n## Connecting from ChatGPT\n\nNeeds ChatGPT Plus\u002FPro (or Business\u002FEnterprise\u002FEdu) with **Developer mode** for custom connectors.\n\n1. ChatGPT → Settings → Apps & Connectors (or Security) → enable **Developer mode**\n2. Create a custom connector \u002F app → paste the same MCP URL (`https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fmcp`)\n3. Auth: **OAuth** → **Advanced OAuth settings** → set **Registration URL** to `https:\u002F\u002Fyour-machine.your-tailnet.ts.net\u002Fregister` (the panel prints the exact value to copy). This is the step that enables DCR: ChatGPT self-registers its own client from it. Skip it and ChatGPT can't register, so it falls back to a user-defined client — and pasting Claude's Client ID there fails, because that client only allows `claude.ai` redirects.\n4. Leave registration method on **DCR**, token endpoint auth method **none** — do **not** paste Claude's Client ID\u002FSecret here.\n5. Enter the same **passphrase** on the confirmation page\n\nSame folder allowlist and shell allowlist as Claude. Restart `npm start` after upgrading so gatekeeper advertises `registration_endpoint` and serves `\u002F.well-known\u002Fopenid-configuration` (ChatGPT reads that to auto-fill the Registration URL).\n\n## Connecting from Grok and Gemini\n\nBoth ride the same MCP URL and passphrase flow — no separate transport or auth. They differ in *how* the client authenticates, and the connector panel (section 1) prints the exact copy fields for each.\n\n**Grok — verified, production-ready:** **self-registers** via the `\u002Fregister` DCR path like ChatGPT — paste only the MCP URL, no Client ID. Its real `redirect_uri` `https:\u002F\u002Fgrok.com\u002Fconnectors-oauth-exchange-code\u002F` was observed live 2026-08-09 and is allowlisted via `GROK_CALLBACK_PREFIX`. Verified working end to end (`authorize → token` 200). If a future Grok change moves that callback, a rejected registration logs `register REJECTED (redirect_uri not allowlisted): [...]` so the new value can be re-allowlisted.\n\n**Gemini — experimental, connection works but tool use doesn't (yet)** (paid tiers only — Pro \u002F Business \u002F Enterprise; the free tier may not expose custom apps): pastes a **confidential client**, exactly like Claude — set the custom app link to the MCP URL, then under Advanced Settings paste the same Client ID \u002F Client secret. Gemini's redirect goes through Google's OAuth proxy `https:\u002F\u002Foauth-redirect.googleusercontent.com\u002Fr\u002F...` (observed live 2026-08-09), allowlisted by `isAllowedRedirect` in `scripts\u002Foauth.js`. **Caveat:** the OAuth handshake succeeds and Gemini accepts the instruction, but in repeated testing 2026-08-09 it did not reliably discover or drive the MCP tools — connection healthy, tool use unreliable. Claude and Grok are the dependable clients today.\n\n## Autonomous Cloud Automation (Grok + Local MCP)\n\nGrok's scheduled prompts turn your machine into a headless \"personal remote AI node\": no browser tab, no desktop app, just `npm start` running in the background.\n\n- **Cloud-triggered local execution:** set up a scheduled prompt in Grok (Automation) that fires at a fixed time.\n- **Headless:** Grok's cloud service sends the request to `\u002Fmcp` over your Tailscale Funnel URL, and `aki-mcp-sv` runs the task — health check, log sweep, `git pull`, cleanup — with nothing open on your end.\n- **Zero UI required:** as long as the process is running, no browser or app needs to be open for the scheduled task to execute.\n\n## Requirements\n\n- Node.js, on Windows, Linux, or macOS. Don't have it? Skip straight to [the standalone package](#install) below, no install needed — bootstrap launchers ship for Windows, Linux, and macOS.\n- **Windows only:** [Git for Windows](https:\u002F\u002Fgit-scm.com\u002Fdownload\u002Fwin) (or WSL) on `PATH` — the shell\u002Fsearch tools shell out to Unix binaries (`ls cat pwd grep head tail wc file stat tree ps df du whoami uname`), and akidevrule's `install.sh` needs `bash`; Git for Windows' `usr\u002Fbin` ships the coreutils\u002Ffindutils\u002Fgrep\u002Fdiffutils this needs. Same category of prerequisite as Tailscale below, not a code dependency.\n- Tailscale (one-time setup):\n  1. [Install Tailscale](https:\u002F\u002Ftailscale.com\u002Fdownload) and sign in (on macOS, the app or `brew install tailscale` both work as long as `tailscale` is on PATH)\n  2. Enable [Funnel](https:\u002F\u002Ftailscale.com\u002Fdocs\u002Ffeatures\u002Ftailscale-funnel) for your tailnet: free on every plan, a one-time toggle via the `login.tailscale.com\u002Ff\u002Ffunnel` link `npm start` prints if it isn't on yet\n\nAfter that, `npm start` enables Funnel on port 9999 automatically every run.\n\n## Architecture\n\n```\nClaude web \u002F ChatGPT\n      │  HTTPS + OAuth 2.1 (Claude: paste client ID\u002Fsecret; ChatGPT: DCR self-register)\n      ▼\nTailscale Funnel        (https:\u002F\u002Fyour-machine.your-tailnet.ts.net)\n      │\n      ▼\ngatekeeper.js  — public port 9999\n      │           \u002F.well-known\u002Foauth-* + openid-configuration  metadata (openid is an alias for ChatGPT discovery)\n      │           \u002Fauthorize, \u002Ftoken    minimal authorization server (scripts\u002Foauth.js)\n      │           \u002Fregister         RFC 7591 dynamic client registration (ChatGPT self-registers here)\n      │           \u002Fmcp                  requires a valid Bearer access token, else 401\n      │                                 POST → real Streamable HTTP (scripts\u002Fstreamable-bridge.js)\n      ▼\nmcp-hub        — internal only (loopback), port 19999, legacy HTTP+SSE transport\n      │\n      ├─► filesystem server    (@modelcontextprotocol\u002Fserver-filesystem — read\u002Fwrite inside the allowed folders)\n      └─► local-tools-mcp.js   — one process hosting 4 in-house tools (local__*):\n                                  search-mcp.js  (find_path\u002Fsearch_content, whole-tree in one call)\n                                  shell-mcp.js   (allowlisted commands, curated to read-only)\n                                  agy-mcp.js     (Antigravity CLI, read-only plan mode)\n                                  kiro-mcp.js    (kiro_read, read-only, needs kiro-cli on PATH)\n\npanel.js       — 127.0.0.1:9998, never exposed via Funnel\n                 control UI: allowed folders, shell allowlist, restart hub,\n                 install akidevrule, generate the connector prompt\n```\n\nThe ingress layer is swappable: Tailscale Funnel is the zero-config default, but the same `\u002Fmcp` endpoint can instead be served through your own Cloudflare named tunnel or any stable public HTTPS edge you already run — see [Exposing to the internet](#exposing-to-the-internet). Everything below the ingress line (gatekeeper, OAuth, mcp-hub) is unchanged whichever edge you pick.\n\n`mcp-hub` ships its own unauthenticated admin REST API (`\u002Fapi\u002F*`) on the same port. `gatekeeper.js` exists specifically so that never reaches the internet (`docs\u002Fplan\u002Fdone\u002Finit.md`).\n\nOAuth (not token-in-URL) is used because claude.ai always attempts Dynamic Client Registration regardless of configuration (`docs\u002Fresearch\u002Fclaude-ai-oauth-connector.md`). ChatGPT also expects OAuth; this server advertises `\u002Fregister` (RFC 7591 DCR) so ChatGPT can self-register while Claude can keep using the pre-issued Client ID\u002FSecret.\n\n## Directory layout\n\n```\naki-mcp-sv\u002F\n├── package.json\n├── mcp-hub.config.json         # shipped default, uses ${MCP_DATA_DIR}\u002F${HOME} placeholders\n├── scripts\u002F\n│   ├── start.js                 # orchestrates mcp-hub + gatekeeper\n│   ├── open-browser.js           # cross-platform \"open default browser\" — the one per-OS seam, no external dep\n│   ├── gatekeeper.js             # OAuth-gated reverse proxy, public port\n│   ├── oauth.js                  # minimal authorization server (pre-registered client + RFC 7591 DCR)\n│   ├── streamable-bridge.js      # Streamable HTTP shim \u003C-> mcp-hub's legacy SSE transport\n│   ├── http.js                   # shared HTTP helpers: readBody \u002F json \u002F serveStatic (+ MIME)\n│   ├── local-tools-mcp.js        # one process hosting shell\u002Fagy\u002Fkiro\u002Fsearch as register(server) modules\n│   ├── shell-mcp.js              # allowlist-gated shell tool (curated to read-only)\n│   ├── agy-mcp.js                # register() module for the agy CLI (mounted by local-tools-mcp.js)\n│   ├── kiro-mcp.js               # Kiro arm: kiro_read (read-only) tool, sonnet-4.5 locked, needs kiro-cli on PATH\n│   ├── mcp-tool.js               # shared MCP tool-result envelope: ok \u002F err \u002F fail\n│   ├── allowlist.js              # default command set + settings reader — shared by server and panel\n│   ├── search-mcp.js             # find_path \u002F search_content — whole tree in one call\n│   ├── roots.js                  # path containment shared by every filesystem-touching tool\n│   ├── tailscale.js              # reads Funnel status — shared by start.js and panel\n│   ├── update-check.js           # checks for newer aki-mcp-sv\u002Fakidevrule versions, shown in the panel\n│   ├── log.js                    # shared timestamped logger\n│   ├── panel.js                  # loopback-only control panel (:9998), token-gated\n│   ├── config-page.js            # renders the panel page\n│   ├── html.js                   # HTML escaper (esc) — shared by oauth confirm page and panel\n│   ├── userdata.js               # user data location (~\u002F.aki\u002Fmcpsv) — single source of truth\n│   └── build\u002F                    # standalone release builder: payload\u002Flaunchers\u002Fchecksums, smoke-test, release-gate\n└── public\u002F                       # panel CSS\u002FJS, favicon + images, served publicly by gatekeeper\n```\n\nYour data lives outside the repo, at `~\u002F.aki\u002Fmcpsv\u002F` (the same convention CLIs like `~\u002F.aws` or `~\u002F.docker` use):\n\n```\n~\u002F.aki\u002Fmcpsv\u002F\n├── mcp-hub.config.json   # live config (which folders you granted access to)\n├── setting.json          # shell allowlist, edited from the panel\n├── oauth-client.json     # pre-issued client ID + secret, for Claude (0600)\n├── oauth-dcr-clients.json # clients that self-registered via \u002Fregister, one per ChatGPT connector (0600)\n├── passphrase.txt        # passphrase for the \u002Fauthorize consent screen (0600)\n└── tokens.json           # access\u002Frefresh tokens (0600)\n```\n\nA clone stays exactly as checked out: editing folders\u002Fallowlist from the panel never produces a diff in the repo.\n\n## Configuration\n\nCopy `.env.example` to `.env` and uncomment what you need — `start.js` loads it automatically on boot (falls back silently to defaults when `.env` is absent, so the default Tailscale flow is unaffected). Supported vars: `PUBLIC_ORIGIN`, `GATEKEEPER_PORT`, `PANEL_PORT`, `MCP_HUB_PORT`, `MCP_DATA_DIR`, `MCP_REQUEST_TIMEOUT_MS`. For a one-off alternate profile, pass `node --env-file=.env.user .\u002Fscripts\u002Fstart.js` instead.\n\n**Standalone package:** `.env.example` isn't part of the downloaded payload, so create `.env` by hand instead, in the same per-version app directory the launcher runs from (not the folder you downloaded the launcher into):\n- macOS: `~\u002FLibrary\u002FApplication Support\u002Faki-mcp-sv\u002Fapp\u002F\u003Cversion>\u002F.env`\n- Linux: `${XDG_DATA_HOME:-~\u002F.local\u002Fshare}\u002Faki-mcp-sv\u002Fapp\u002F\u003Cversion>\u002F.env`\n- Windows: `%LOCALAPPDATA%\\aki-mcp-sv\\app\\\u003Cversion>\\.env`\n\n## Exposing to the internet\n\nTailscale Funnel is the default, zero-config path and stays the recommended flow. If Funnel is unreliable for you, two alternative ingress options let you bring your own public edge instead — see [Alternative ingress](#alternative-ingress-if-funnel-is-unreliable) below.\n\n`npm start` enables Funnel automatically when needed (see above), no manual step. Funnel is state stored in `tailscaled` (survives reboots), independent of `npm start`'s own lifecycle; disable it entirely with `tailscale funnel 9999 off`.\n\n**Know before enabling Funnel:**\n- Free on every Tailscale plan, but the tailnet needs a one-time opt-in first (the `login.tailscale.com\u002Ff\u002Ffunnel?node=...` link `tailscale funnel --bg` prints if it's missing).\n- Only 3 ports are fundeable: `443`, `8443`, `10000`; you can't expose an arbitrary port.\n- Bandwidth is limited; Tailscale doesn't publish an exact number.\n- Don't toggle Funnel on\u002Foff repeatedly: re-issuing the certificate too often can hit Let's Encrypt's rate limit (~34h lockout). `start.js` avoids this by checking `Web[].Handlers[].Proxy` for port 9999 in `tailscale funnel status --json` before deciding Funnel is off (not the `AllowFunnel` key, which reflects the public port 443, not 9999).\n\n**Diagnosing \"claude.ai can't connect\" while `tailscale funnel status` says \"on\":** the serve-config can save locally but fail to sync to Tailscale's control plane, so a real client on the open internet is blocked at the TLS layer while the host machine, routed through the internal mesh, sees everything as fine. **Don't test with a bare `curl https:\u002F\u002F\u003Chost>` from the machine running `npm start`**: that machine is in the tailnet and silently takes the mesh shortcut. Test the real path instead:\n\n```bash\ndig @8.8.8.8 \u003Chost> A +short   # real public IP\ncurl --resolve \u003Chost>:443:\u003CIP-from-above> https:\u002F\u002F\u003Chost>\u002F.well-known\u002Foauth-authorization-server\n```\n\nIf that returns `SSL_ERROR_SYSCALL`\u002Ftimeout despite `tailscale funnel status` saying \"on\", re-run `tailscale funnel --bg 9999` to force a config re-push (not a code bug). Full writeup: `docs\u002Fresearch\u002Fclaude-ai-oauth-connector.md`, section \"Debug round 5\".\n\n### Alternative ingress (if Funnel is unreliable)\n\nThe Funnel edge can intermittently drop individual requests in some regions. The drop-rate difference against Cloudflare is still unmeasured, so these are not a proven upgrade — reach for them only if Funnel is unreliable for you. Both replace the Tailscale edge entirely; the OAuth server and tool suite are unchanged. Precedence when more than one is set: `--tunnel` > `PUBLIC_ORIGIN` > saved panel config (section 0 → \"Owned public origin\") > Tailscale Funnel. Full rationale: `docs\u002Fplan\u002Fcloudflare-tunnel-ingress.md`.\n\n**Bring your own edge (`PUBLIC_ORIGIN`):** point an env var at a stable public HTTPS origin you run and terminate yourself, and `npm start` skips Tailscale entirely, serving at that origin:\n\n```bash\nPUBLIC_ORIGIN=https:\u002F\u002Fyour-host npm start\n```\n\n**Cloudflare named tunnel (`--tunnel`):** the server launches a Cloudflare named tunnel for you, reading `TunnelID` from a cloudflared credentials JSON and running `cloudflared tunnel run` forwarding to `127.0.0.1:9999`:\n\n```bash\nnpm start -- --tunnel \u003Ccred.json> --origin https:\u002F\u002Fyour-host\n```\n\n`--origin` is **required** because a credentials JSON carries no hostname. This is JSON-credentials mode only — no `yml` config, no token. Before it works you need a Cloudflare account, a named tunnel already created (`cloudflared tunnel create`), its credentials JSON, and a DNS route pointing the hostname at that tunnel.\n\n**Someone gave you a tunnel JSON:** if a host who owns the domain already created the tunnel and DNS route and sent you the credentials JSON, you need no Cloudflare account of your own — just install `cloudflared`, then run with the origin they assigned:\n\n```bash\nnpm start -- --tunnel \u003Cthe-json-they-sent> --origin https:\u002F\u002Fthe-subdomain-they-gave-you\n```\n\nTo get a subdomain under a host's domain, arrange it with them directly; there is no self-serve signup.\n\n**From the panel (no CLI flags):** open the control panel → section 0 → \"Owned public origin\" tab → upload your cloudflared credentials JSON and the hostname you routed it to → Save. Takes effect on the next `npm start` (restart required, not a live switch); a \"Use Tailscale Funnel instead\" button reverts it.\n\nWhen a custom ingress is active, the panel's section 0 skips the Tailscale checks and instead shows the active ingress and the serving origin — so the absent Tailscale UI is expected, not a fault.\n\n## Finding files\n\nUse `local__find_path`, not `filesystem__search_files`, to locate a file or directory. The built-in `search_files` doesn't return directories and tends to time out on large trees, so a remote session can appear to \"not see\" the very project it has access to. `find_path` scans the whole tree in one call (measured: ~0.2s across 164k files \u002F 11.7k directories), returns **both files and directories**, and skips `node_modules`\u002F`.git`\u002Fbuild output automatically. `query` is a case-insensitive substring, or a glob when it contains `*`\u002F`?`.\n\n## Security\n\nMinimal OAuth 2.1: Claude uses a pre-issued confidential Client ID\u002FSecret; ChatGPT uses DCR (`POST \u002Fregister`) as a public client (`token_endpoint_auth_method: none`) with `chatgpt.com` redirect URIs allowlisted. Full writeup: `docs\u002Fref\u002Fsecurity-model.md`.\n\n- `$MCP_DATA_DIR` (default `$HOME`, reaching your whole home folder: Desktop, Documents, Downloads, Photos, everything under it, not just projects) is the filesystem server's main root, plus `~\u002F.aki` and `~\u002F.claude` (for native rule files), fixed at process start; changing it via the panel restarts the hub. `~\u002F.claude` is granted at the folder level, so session tokens and chat history inside it are also in the connector's reach (a known tradeoff; the panel row is locked and can't be removed there: edit `~\u002F.aki\u002Fmcpsv\u002Fmcp-hub.config.json` before connecting if you want it out).\n- The shell MCP is hand-written (`shell-mcp.js`), enforcing the allowlist in code (`execFile`, never through a shell, `; & | \\`` blocked). The default set is read-only, defined in `allowlist.js` — flag-rich binaries whose own flags escape read-only (`find -delete`\u002F`-exec`, `sort -o \u003Cpath>`) are deliberately kept out of it (issue #2), so a default connector cannot write, delete, or exec through the shell tool; the `find_path`\u002F`search_content` tools cover the read-only lookup they were used for. The panel shows exactly that set as your starting point for edits, saved to `~\u002F.aki\u002Fmcpsv\u002Fsetting.json` → `shell.allowlist`. **Any command you add is your own responsibility**: adding an obvious write command (e.g. `git commit`) widens the surface further. A command can run in any directory under the allowed roots via the `cwd` parameter, used instead of `cd`\u002F`-C` to target a specific repo.\n- `gatekeeper.js` is the single public entry point; the real `mcp-hub` never listens on anything but loopback.\n- `panel.js` writes config and runs commands on your machine, so it **only binds to `127.0.0.1`** and is never exposed via Funnel. Its token is regenerated every `npm start` and required both in the page's query string and in the `x-panel-token` header on every API call, blocking other browser tabs from POSTing to it.\n- `~\u002F.aki\u002Fmcpsv\u002Fpassphrase.txt` (the `\u002Fauthorize` consent passphrase) and `~\u002F.aki\u002Fmcpsv\u002Foauth-client.json` (client ID\u002Fsecret) are mode 0600, live outside the repo (never reach git), and are only ever shared once, pasted into the connector dialog.\n- Access\u002Frefresh tokens live in `~\u002F.aki\u002Fmcpsv\u002Ftokens.json` (mode 0600) and survive restarts: a connector is long-lived file access, not a login session, so losing tokens on every `npm start` would just force pointless re-authentication. Access token TTL is 1 year, refresh tokens don't expire. Revoke by deleting `~\u002F.aki\u002Fmcpsv\u002Ftokens.json` and restarting.\n- Each ChatGPT connector instance self-registers one client into `~\u002F.aki\u002Fmcpsv\u002Foauth-dcr-clients.json` (mode 0600). Registration is open but not a way in on its own: only `claude.ai` and `chatgpt.com` redirect URIs are accepted, and a registered client still has to pass the passphrase consent screen and PKCE before it gets a token. Revoke those registrations by deleting that file and restarting.\n- Funnel stays enabled in the background for the whole project; `npm start` is the only thing you actively start\u002Fstop.\n\n### How this differs from Desktop Commander\n\n[Desktop Commander](https:\u002F\u002Fgithub.com\u002Fwonderwhy-er\u002FDesktopCommanderMCP) is the most widely used MCP terminal server. It runs locally for **Claude Desktop** and guards shell access with a **blocklist** (`blockedCommands`, an explicit list of forbidden commands). A blocklist is inherently leaky: you can't enumerate every dangerous command and variant, and the default is *allow*: anything not on the list gets through.\n\nThis project targets a different scenario: exposing local access to Claude **on the web**, across the open internet via Funnel. It makes the opposite default choice: a **whitelist**. Nothing runs unless it's explicitly allowed.\n\n### Why whitelist, not blocklist\n\n- **Fail-safe**: an unfamiliar or new command is blocked automatically, no guessing required.\n- **Minimal attack surface**: only the exact commands you've approved can run, nothing more.\n- **Granular down to the subcommand**: `git` is scoped to `status\u002Flog\u002Fdiff\u002Fshow`, something a blocklist can't express cleanly.\n- **Neutralizes prompt injection**: exposed to the open internet, a hard whitelist means a malicious or injected instruction has nothing to escalate to — there's no unlisted command for it to reach for.\n- **Read-only by construction**: the built-in set is read-only — flag-rich binaries that could escape it via their own flags (`find`, `sort`) are kept out (issue #2); adding a write command is a deliberate edit to `~\u002F.aki\u002Fmcpsv\u002Fsetting.json`, not the removal of a ban.\n\n## Screenshots\n\u003Cimg width=\"899\" height=\"1035\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fc7504913-7ff0-4802-b607-b6a6220e82c2\" \u002F>\n\u003Cimg width=\"898\" height=\"834\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F2b64541a-aea8-4bcf-b4dc-341254895a32\" \u002F>\n\u003Cimg width=\"892\" height=\"1032\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F69413798-5445-4277-9797-a671da6657bd\" \u002F>\n\u003Cimg width=\"651\" height=\"701\" alt=\"gpt-aki-mcp-setting\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fc067919c-1b7f-4f49-af81-82f1193f1f17\" \u002F>\n","aki-mcp-sv 是一个轻量级本地 MCP（Model Context Protocol）服务端实现，为 Claude、ChatGPT、Grok 等 Web 版大模型聊天界面提供安全的本地文件读写与受限 Shell 命令执行能力。它通过 HTTPS 边缘隧道（如 Tailscale Funnel 或 Cloudflare Tunnel）暴露服务，采用 OAuth 2.1 认证，并默认启用白名单制命令控制，无需安装桌面客户端或绑定设备。适用于远程办公、多账号协同开发、跨设备项目调试等场景，尤其适合在非主力工作机（如笔记本、临时环境）上快速启用模型对本地项目的上下文感知能力。",2,"2026-08-12 02:30:14","CREATED_QUERY"]