{
  "markdown": "# ToolMesh — Let AI agents touch real systems. Safely.\n\n> The missing control layer between AI agents and enterprise systems. ToolMesh turns uncontrolled AI tool calls into a governed, auditable process — and connects any REST API or MCP server in minutes, not months.\n\n[![Go](https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go)](https://go.dev)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)\n[![CI](https://github.com/DunkelCloud/ToolMesh/actions/workflows/ci.yml/badge.svg)](https://github.com/DunkelCloud/ToolMesh/actions/workflows/ci.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/DunkelCloud/ToolMesh)](https://goreportcard.com/report/github.com/DunkelCloud/ToolMesh)\n\n## 30 lines of YAML. No server to build.\n\nIn practice, MCP servers only expose a fraction of the REST API they wrap — and you'll hit the gaps fast. ToolMesh lets you replace the wrapper layer with `.dadl` files — a declarative YAML format that describes any REST API as MCP tools. No wrapper server to build, deploy, or maintain.\n\n```\nCurrent:    Claude → ToolMesh → MCP Server → REST API\nWith DADL:  Claude → ToolMesh → REST API (via .dadl file)\n```\n\nYou don't write the YAML by hand. You ask an LLM. Claude, GPT, Gemini — any model that knows the DADL spec generates a working `.dadl` file in seconds. Describe what you need, drop the file into `config/dadl/`, done.\n\n> \"Create a DADL for the GitHub API — list repos, open issues, and create pull requests.\"\n\n10 seconds. Works with any LLM that knows the format.\n\nAnd unlike MCP gateways that just pass tool calls through, ToolMesh adds what production deployments actually need:\n\n- **Credential Security** — secrets injected at execution time, never in prompts or LLM client configs\n- **Authorization** — fine-grained user → plan → tool control (OpenFGA)\n- **Input & Output Gating** — JS policies block confidential data and filter responses\n- **Audit Trail** — every tool call recorded with structured logging or queryable SQLite\n\n## The Six Pillars\n\n| Pillar | What it does | Backed by |\n|--------|-------------|-----------|\n| **Any Backend** | 30 lines of DADL replace a whole MCP server. Also proxies existing MCP servers. | Go MCP SDK + DADL (.dadl files) |\n| **Code Mode** | 15 MCP servers at once? Without ToolMesh, impossible. Code Mode cuts 50,000+ tokens to ~1,000. | AST-parsed tool calls |\n| **Credential Store** | Secrets injected at execution time — never in prompts, never in LLM client configs | Per-request injection via Executor pipeline |\n| **OpenFGA** | Fine-grained authorization (user → plan → tool). Example: free users get read-only, pro gets everything. | OpenFGA |\n| **Gate** | Block confidential data before execution, redact PII in responses | goja |\n| **Audit** | Every tool call recorded and queryable — answer \"what did that agent do?\" with SQL | slog / SQLite |\n\n## Try the demo\n\nWant to try ToolMesh before installing? Connect to our public demo instance — no Docker, no config, no API keys:\n\n**[demo.toolmesh.io](https://www.toolmesh.io/en/demo/)** — Hacker News APIs via ToolMesh. Works with Claude Desktop, Claude Code, and ChatGPT. Login: `dadl` / `toolmesh`.\n\n## Quickstart\n\n```bash\n# Clone\ngit clone https://github.com/DunkelCloud/ToolMesh.git\ncd ToolMesh\n\n# Configure\ncp .env.example .env\n# IMPORTANT: Set a password — without it, all requests are rejected:\n#   TOOLMESH_AUTH_PASSWORD=my-secret-password\n# Or set an API key for programmatic access:\n#   TOOLMESH_API_KEY=my-api-key\n\n# Optional: local overrides (build locally, enable OpenFGA, HTTPS proxy, ...)\n# cp docker-compose.override.yml.example docker-compose.override.yml\n# # then edit docker-compose.override.yml — picked up automatically by Docker Compose\n\n# Start (fine-grained authz is in bypass by default; the password/API key\n# above is still required — without it every request is rejected)\ndocker compose up -d\n\n# Verify it's running (default port: 8123)\ncurl http://localhost:8123/health\n\n# MCP endpoint: http://localhost:8123/mcp\n# Note: Most MCP clients require HTTPS — see TLS section below\n```\n\n### TLS (important)\n\nToolMesh itself serves plain HTTP. **Most MCP clients — including Claude Desktop — require HTTPS** and will reject `http://` URLs. You need a TLS-terminating reverse proxy in front of ToolMesh:\n\n| Option | When to use |\n|--------|-------------|\n| **Caddy** | Self-hosted with a public domain — automatic Let's Encrypt certs |\n| **Cloudflare Tunnel** | No open ports needed, zero-config TLS |\n| **nginx / Traefik** | Already in your stack |\n\nFor **local development only**, you can bypass TLS by editing `claude_desktop_config.json` by hand (the GUI enforces `https://`).\n\n### Connect to Claude Desktop\n\nAdd to your Claude Desktop MCP config:\n\n```json\n{\n  \"mcpServers\": {\n    \"toolmesh\": {\n      \"url\": \"https://toolmesh.example.com/mcp\"\n    }\n  }\n}\n```\n\nFor local development without TLS proxy:\n\n```json\n{\n  \"mcpServers\": {\n    \"toolmesh\": {\n      \"url\": \"http://localhost:8123/mcp\"\n    }\n  }\n}\n```\n\n### Connect to Claude.ai (Custom Connector)\n\nToolMesh supports OAuth 2.1 with PKCE S256 for remote access. Configure users in `config/users.yaml` and use the public HTTPS URL as the MCP endpoint.\n\n## Authentication\n\nToolMesh supports two authentication methods that can be used independently or together. All OAuth state (tokens, auth codes, clients) is persisted in Redis and survives server restarts.\n\n### OAuth 2.1 (Interactive Login)\n\nDefine users in `config/users.yaml` with bcrypt-hashed passwords:\n\n```yaml\nusers:\n  - username: admin\n    password_hash: \"$2a$10$...\"\n    company: dunkelcloud\n    plan: pro\n    roles: [admin]\n```\n\nGenerate password hashes with any bcrypt-capable utility:\n\n```bash\nhtpasswd -nbBC 10 \"\" \"my-password\" | cut -d: -f2\n```\n\nFor single-user setups, `TOOLMESH_AUTH_PASSWORD` still works as a fallback. Configure the identity with `TOOLMESH_AUTH_USER`, `TOOLMESH_AUTH_PLAN`, and `TOOLMESH_AUTH_ROLES` (defaults: `owner`, `pro`, `admin`).\n\n### API Keys (Programmatic Access)\n\nDefine API keys in `config/apikeys.yaml` with bcrypt-hashed keys:\n\n```yaml\nkeys:\n  - key_hash: \"$2a$10$...\"\n    user_id: claude-code-user\n    company_id: dunkelcloud\n    plan: pro\n    roles: [tool-executor]\n```\n\nEach key maps to a distinct user identity with its own plan and roles, which flow through to OpenFGA authorization.\n\nFor single-key setups, `TOOLMESH_API_KEY` still works as a fallback. The same `TOOLMESH_AUTH_USER`, `TOOLMESH_AUTH_PLAN`, and `TOOLMESH_AUTH_ROLES` variables control the identity.\n\n### DCR Rate Limiting\n\nDynamic Client Registration is rate-limited to 5 registrations per hour per IP to prevent abuse.\n\n## Authorization Mode\n\n`OPENFGA_MODE` controls whether OpenFGA authorization is enforced:\n\n| Mode | Behavior |\n|------|----------|\n| `bypass` (default) | All tool calls are allowed without authz checks |\n| `restrict` | OpenFGA enforces user → plan → tool authorization (requires `OPENFGA_STORE_ID`) |\n\nStart with `bypass` to get running quickly, then switch to `restrict` after bootstrapping OpenFGA.\n\n### Security posture at startup\n\nToolMesh is secure-by-default: it ships with no individual default that relaxes a control silently. At boot it logs a single **security-posture summary** that lists every control still in a relaxed state (missing auth credential, authz bypass, open CORS, debug tools) together with how to harden it. In the default production posture these are logged at `WARN`; set `TOOLMESH_DEV=true` on a local-development machine to report the same facts once at `INFO` so the warnings don't become background noise. `TOOLMESH_DEV` changes only the log level of this summary — it never relaxes a setting on its own.\n\n## Configuration\n\nSee [docs/configuration.md](docs/configuration.md) for all environment variables.\n\n### Timeout tuning\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `TOOLMESH_MCP_TIMEOUT` | `120` | HTTP client timeout (seconds) for calls to downstream MCP servers |\n| `TOOLMESH_EXEC_TIMEOUT` | `120` | Tool execution timeout (seconds) — context deadline for backend calls |\n\nIncrease these for backends that need more time (e.g. browser-based web fetchers):\n\n```bash\nTOOLMESH_MCP_TIMEOUT=180\nTOOLMESH_EXEC_TIMEOUT=180\n```\n\n### Logging\n\nToolMesh uses structured logging via `slog`. The default level is **`info`**. Set `LOG_LEVEL=debug` to trace the full MCP request/response flow when diagnosing a problem — but note that debug logs include complete request URLs and payloads, so for query-string API keys the credential ends up in the log. Keep `debug` off in production. Per-backend debug files, log formats, and all logging variables are documented in [docs/configuration.md](docs/configuration.md#logging).\n\n### Metrics (Prometheus)\n\nToolMesh exposes Prometheus metrics on a separate listener (default host port 9090) — login counts by method and result, tool-call rates by backend and outcome, and a latency histogram with REST-tuned buckets. The endpoint is unauthenticated, so the Compose file binds the host port to `127.0.0.1` by default (reachable locally and from Prometheus on the same Docker network, never from a public interface). Set `TOOLMESH_METRICS_HOST=0.0.0.0` only if you scrape from another host and have firewalled the port yourself.\n\nConfigure via `TOOLMESH_METRICS_PORT`, `TOOLMESH_METRICS_ENABLED`, and `TOOLMESH_METRICS_LABEL_TOOL` in `.env`. See [docs/metrics.md](docs/metrics.md) for the full schema, example PromQL queries, and a sample scrape configuration.\n\n### Debug tools (opt-in)\n\nSet `TOOLMESH_DEBUG_TOOLS=true` in `.env` to expose two diagnostic MCP tools — `debug_echo` (returns size and SHA-256 of the supplied payload, no backend) and `debug_generate` (produces a printable string of N bytes). Use them to probe transport-level size limits between the calling LLM and ToolMesh, or to verify round-trip byte integrity. Off by default; intended for development and incident triage, not production.\n\n## Architecture\n\nSee [docs/architecture.md](docs/architecture.md) for the full architecture documentation.\n\n```\n                          ┌─────────────────────────────────┐\n                          │          ToolMesh               │\n                          │                                 │\n                          │  Redis · OpenFGA · Audit        │\n                          │  Credential Store · JS Gate     │\n                          │                                 │\nAI Agent ──MCP──────────▶ │   AuthZ ▸ Creds ▸ Gate ▸ Exec  │\n                          │                                 │\n                          └──┬──────┬───────┬───────┬───────┘\n                             │      │       │       │\n                          MCP Client  .dadl   .dadl   .dadl\n                             │      │       │       │\n                             ▼      ▼       ▼       ▼\n                          MCP     Stripe  GitHub  Vikunja\n                          Server   API     API     API\n```\n\n## Adding an External MCP Server\n\nCreate or edit `config/backends.yaml`:\n\n```yaml\nbackends:\n  - name: memorizer\n    transport: http\n    url: \"https://memorizer.example.com/mcp\"\n    api_key_env: \"MEMORIZER_API_KEY\"\n```\n\nSet the credential as an environment variable:\n\n```bash\nCREDENTIAL_MEMORIZER_API_KEY=sk-mem-xxxxx\n```\n\nTools from each backend are exposed with a prefix (e.g. `memorizer_retrieve_knowledge`). Credentials are injected by the Executor at runtime via the CredentialStore — the LLM never sees API keys.\n\n## REST Proxy Mode ([DADL](https://dadl.ai))\n\nWhen an MCP server doesn't expose an endpoint you need, describe it in a `.dadl` file and ToolMesh calls the REST API directly — no wrapper server needed. Both modes run in parallel.\n\nAdd a REST backend to `config/backends.yaml`:\n\n```yaml\nbackends:\n  - name: vikunja\n    transport: rest\n    dadl: /app/dadl/vikunja.dadl\n    url: \"https://vikunja.example.com/api/v1\"\n```\n\nFor internal services with private IPs or self-signed certificates:\n\n```yaml\nbackends:\n  - name: internal-api\n    transport: rest\n    dadl: internal.dadl\n    url: \"https://192.168.1.50:8443/api\"\n    allow_private_url: true    # allow private/loopback addresses (default: true)\n    tls_skip_verify: true      # accept self-signed certificates (default: false)\n```\n\nWant Claude to list GitHub issues? Here's all it takes:\n\n```yaml\ntools:\n  list_issues:\n    method: GET\n    path: /repos/{owner}/{repo}/issues\n    description: \"List issues for a repository\"\n    params:\n      owner: { type: string, in: path, required: true }\n      repo:  { type: string, in: path, required: true }\n      state: { type: string, in: query }\n```\n\nToolMesh handles auth, pagination, retries, and error mapping. DADL supports bearer tokens, OAuth2, session auth, API keys, automatic pagination, retry with backoff, response transformation, composite tools, and more.\n\nFor the full spec, examples, and the community registry, see [dadl.ai](https://dadl.ai). The fastest way to create a `.dadl` file is asking any LLM that knows the format.\n\n## Code Mode\n\nConnect 15 MCP servers to a single AI agent? Without ToolMesh, that simply does not work — the context window fills up, the client chokes. Code Mode makes it possible.\n\nInstead of exposing hundreds of individual tool definitions (50,000+ tokens), ToolMesh exposes two meta-tools: `discover_tools` and `execute_code`. The LLM gets compact TypeScript interfaces (~1,000 tokens) and writes JavaScript against them:\n\n```javascript\nconst repos = await toolmesh.github_list_repos({ sort: \"updated\" });\nconst issues = await toolmesh.github_list_issues({\n  owner: repos[0].owner.login,\n  repo: repos[0].name,\n  state: \"open\"\n});\n```\n\nMultiple API calls in a single round-trip. ToolMesh parses the code, extracts tool calls, and routes them through the full execution pipeline.\n\n## Extension Model\n\nToolMesh uses a registry-based extension model inspired by Go's `database/sql` driver pattern. Three component types are extensible via `init()` registration:\n\n| Component | Built-in | Config |\n|-----------|----------|--------|\n| Credential Store | `embedded` | `CREDENTIAL_STORE=<name>` |\n| Tool Backend | `mcp`, `rest` (DADL), `echo` | `config/backends.yaml` |\n| Gate Evaluator | `goja` | `GATE_EVALUATORS=<list>` |\n\nEnterprise extensions (InfisicalStore, VaultStore, Compliance-LLM, etc.) are planned and will be included via Go build tags: `go build -tags enterprise ./cmd/toolmesh`.\n\nSee [docs/architecture.md](docs/architecture.md#extension-model) for details.\n\n## Who's behind this\n\nToolMesh is built and maintained by **[Axel Dunkel](https://github.com/axeldunkel)**\nat **[Dunkel Cloud GmbH](https://dunkel.cloud)** — a German company building\ninfrastructure that connects AI agents to real systems. ToolMesh and DADL grew\nout of the production cloud and edge infrastructure we've run for businesses for\nyears, so this isn't a side project behind an anonymous handle: it runs in our\nown stack, and the same people who answer `security@dunkel.cloud` write the code.\n\nThe companion **[DADL registry](https://dadl.ai/browse)** currently ships\n**27 community API descriptions covering ~3,300 generated tools** — from a\n4-tool Hacker News reader to a 608-tool NetBox DCIM/IPAM surface — each with a\nvisible API-coverage percentage so you can see exactly how complete it is.\n\n- 🌐 Website: [toolmesh.io](https://www.toolmesh.io/)\n- 📖 DADL spec & registry: [dadl.ai](https://dadl.ai)\n- 🐛 Security contact: security@dunkel.cloud (see [SECURITY.md](SECURITY.md))\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nApache 2.0 — Copyright 2025–2026 [Dunkel Cloud GmbH](https://dunkel.cloud)\n",
  "bytes": 15640,
  "sha": "a0d4d6158de1626adc4c0edd08b8fdad7c69712ec97697d202bf7015800a5591",
  "repo_slug": "dunkelcloud/toolmesh",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dunkelcloud_toolmesh_207b1dda/readme"
}