Tailscale-native MCP gateway with identity-based access control, audit logging, and session recording
at main 173 lines 5.0 kB view raw view rendered
1# Turnscale 2 3[![tangled.sh](https://img.shields.io/badge/tangled.sh-scottlanoue.com%2Fturnscale-blue)](https://tangled.sh/scottlanoue.com/turnscale) 4 5MCP gateway for Tailscale. Proxies [Model Context Protocol](https://modelcontextprotocol.io) requests to multiple backends, using Tailscale identity for access control. Single Go binary, no credentials to manage. 6 7- Identity from Tailscale `WhoIs` — no API keys or OAuth 8- Policy via tailnet ACL grants (or YAML fallback) 9- Per-tool deny globs (e.g. allow Gitea but deny `delete_*`) 10- Audit log and optional session recording 11- Dashboard with server health, request chart, tool discovery 12- Single binary, no CGO 13 14![Turnscale dashboard](demo.png) 15 16## Quick Start 17 18```bash 19go build ./cmd/turnscale 20cp gateway.example.yaml gateway.yaml # edit with your servers + policies 21./turnscale -config gateway.yaml 22``` 23 24The gateway joins your tailnet as a node. Point MCP clients at `https://{hostname}.{tailnet}.ts.net/{server}/mcp`. 25 26## Configuration 27 28```yaml 29hostname: "mcp" 30tailnet: "your-tailnet.ts.net" 31state_dir: "~/.local/share/turnscale" 32 33servers: 34 github: 35 url: "http://localhost:8091/mcp" 36 transport: "streamable-http" 37 slack: 38 url: "http://localhost:8092/mcp" 39 transport: "streamable-http" 40 41policies: 42 - name: "admin" 43 match: 44 identity: ["you@github"] 45 allow: ["*"] 46 47 - name: "ai-agents" 48 match: 49 tags: ["tag:ai-agent"] 50 allow: ["github", "slack"] 51 deny_tools: ["mcp__github__delete_*"] 52 53 - name: "default-deny" 54 match: 55 identity: ["*"] 56 deny: ["*"] 57``` 58 59Servers can also be added, edited, and removed from the dashboard UI. 60 61## Connecting Clients 62 63### Claude Code 64 65Add to `~/.claude/settings.local.json`: 66 67```json 68{ 69 "mcpServers": { 70 "github": { 71 "type": "http", 72 "url": "https://mcp.your-tailnet.ts.net/github/mcp" 73 }, 74 "slack": { 75 "type": "http", 76 "url": "https://mcp.your-tailnet.ts.net/slack/mcp" 77 } 78 } 79} 80``` 81 82### Any MCP Client (Streamable HTTP) 83 84``` 85POST https://mcp.your-tailnet.ts.net/{server}/mcp # JSON-RPC 86GET https://mcp.your-tailnet.ts.net/{server}/mcp # SSE stream 87DELETE https://mcp.your-tailnet.ts.net/{server}/mcp # Session termination 88``` 89 90No auth headers — identity comes from Tailscale. 91 92## Tailscale ACL Grants 93 94The recommended way to manage access. Add grants to your tailnet policy — the gateway reads them from `WhoIs().CapMap` at request time: 95 96```json 97{ 98 "grants": [ 99 { 100 "src": ["autogroup:member"], 101 "dst": ["tag:server"], 102 "app": { 103 "your-tailnet.ts.net/cap/mcp": [{ 104 "servers": ["*"], 105 "admin": true 106 }] 107 } 108 }, 109 { 110 "src": ["tag:ai-agent"], 111 "dst": ["tag:server"], 112 "app": { 113 "your-tailnet.ts.net/cap/mcp": [{ 114 "servers": ["github", "slack"], 115 "denyTools": ["mcp__github__delete_*"], 116 "record": true 117 }] 118 } 119 } 120 ] 121} 122``` 123 124| Field | Description | 125|-------|-------------| 126| `servers` | Server names or `["*"]` for all | 127| `denyTools` | Glob patterns for denied tools | 128| `admin` | Access to audit logs, server management, session recordings | 129| `record` | Capture full request/response bodies for this caller | 130 131Grants take priority over YAML policies. Multiple grants per caller are unioned. 132 133## Web UI 134 135All pages live under `/ui/`: 136 137- `/ui/` — dashboard: request chart, server health, policies, recent activity 138- `/ui/audit` — full audit log with filtering by caller/server/tool/status 139- `/ui/session/{id}` — session recording viewer 140 141## Architecture 142 143``` 144┌──────────────┐ ┌──────────────────┐ ┌─────────────┐ 145│ Claude Code │────>│ Turnscale │────>│ github-mcp │ 146│ │ │ (tsnet node) │────>│ slack-mcp │ 147├──────────────┤ │ │────>│ jira-mcp │ 148│ AI Agents │────>│ - WhoIs identity│────>│ ... │ 149│ (tag:agent) │ │ - ACL grants │ └─────────────┘ 150└──────────────┘ │ - Audit + Record│ 151 │ - Tool discovery│ 152 │ - Dashboard │ 153 └──────────────────┘ 154``` 155 156## Development 157 158```bash 159go build ./... # Build 160go test -race ./... # Test (85 tests, race detector) 161go test -cover ./... # Coverage 162go vet ./... # Static analysis 163``` 164 165## Dependencies 166 167- [`tailscale.com/tsnet`](https://pkg.go.dev/tailscale.com/tsnet) — embedded Tailscale node 168- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite) — pure-Go SQLite (no CGO) 169- [`gopkg.in/yaml.v3`](https://pkg.go.dev/gopkg.in/yaml.v3) — config parsing 170 171## License 172 173[MIT](LICENSE)