From 37f8ceaff20f2e77f256413318f3298d28ece32a Mon Sep 17 00:00:00 2001 From: false200 <214800619+false200@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:53:05 +0530 Subject: [PATCH] docs: rewrite README and add community templates - Restructure README for clearer onboarding (install, usage, config) - Add issue templates (bug, feature, question) and PR template - Add repo labels including good first issue and label sync workflow --- .github/ISSUE_TEMPLATE/bug_report.yml | 44 ++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 23 ++ .github/ISSUE_TEMPLATE/question.yml | 17 ++ .github/labels.yml | 48 ++++ .github/pull_request_template.md | 13 + .github/workflows/labels.yml | 20 ++ README.md | 281 +++++++++------------ 8 files changed, 284 insertions(+), 167 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/labels.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/labels.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..4cf1fa9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,44 @@ +name: Bug report +description: Something isn't working +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for reporting. Include version and a redacted config if you can (`npx -y tooltrim validate-config`). + + - type: textarea + id: what-happened + attributes: + label: What happened? + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What did you expect? + validations: + required: true + + - type: input + id: version + attributes: + label: Tooltrim version + placeholder: "0.1.2" + validations: + required: true + + - type: textarea + id: config + attributes: + label: Config (redacted) + description: Paste output of `tooltrim validate-config` or a trimmed `tooltrim.config.yaml` + render: yaml + + - type: textarea + id: logs + attributes: + label: Relevant logs or trace lines + description: A few lines from `.tooltrim/trace.ndjson` if helpful diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..96616b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Contributing guide + url: https://github.com/false200/Tooltrim/blob/main/CONTRIBUTING.md + about: Setup, style, and how to run tests locally diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..1b75ee7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,23 @@ +name: Feature request +description: Suggest a new feature or improvement +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed solution + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..231924a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,17 @@ +name: Question +description: Ask how something works +title: "[Question]: " +labels: ["question"] +body: + - type: textarea + id: question + attributes: + label: Your question + validations: + required: true + + - type: textarea + id: context + attributes: + label: Context + description: MCP client, Node version, transport (stdio/HTTP), etc. diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..51e948e --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,48 @@ +# Synced to GitHub via .github/workflows/labels.yml +- name: bug + color: d73a4a + description: Something isn't working + +- name: enhancement + color: a2eeef + description: New feature or improvement + +- name: documentation + color: 0075ca + description: Docs, README, or examples + +- name: good first issue + color: 7057ff + description: Good for newcomers + +- name: help wanted + color: 008672 + description: Extra attention or skill needed + +- name: question + color: d876e3 + description: Question or support request + +- name: triage + color: fbca04 + description: Needs maintainer review or reproduction + +- name: performance + color: f9d0c4 + description: Speed, memory, or token savings + +- name: security + color: b60205 + description: Security-related issue or fix + +- name: dependencies + color: 0366d6 + description: Dependency updates + +- name: upstream + color: c5def5 + description: Upstream MCP server or SDK behavior + +- name: wontfix + color: ffffff + description: Will not be addressed diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..2a46e5e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Summary + + + +## Checklist + +- [ ] `pnpm typecheck` passes +- [ ] `pnpm test` passes +- [ ] Config or README updated if behavior changed + +## Related issues + + diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 0000000..e3b7dbf --- /dev/null +++ b/.github/workflows/labels.yml @@ -0,0 +1,20 @@ +name: Sync labels + +on: + push: + branches: [main] + paths: + - .github/labels.yml + workflow_dispatch: + +permissions: + issues: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: EndBug/label-sync@v2 + with: + config-file: .github/labels.yml + delete-other-labels: false diff --git a/README.md b/README.md index 42ad009..9631c73 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,146 @@ # Tooltrim -> An open-source [Model Context Protocol](https://modelcontextprotocol.io) proxy that puts your tool list on a diet. -> Aggregate N MCP servers, filter and shrink the noise, and trace every call. - [![npm](https://img.shields.io/npm/v/tooltrim.svg)](https://www.npmjs.com/package/tooltrim) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org) -## Why +⚡ **Drop-in [MCP](https://modelcontextprotocol.io) proxy** — aggregate N upstream servers into one endpoint, filter and shrink the tool list, trace every call, and measure tokens saved. -Connect three or four MCP servers in a single session and the **tool metadata alone routinely eats 40-50% of the context window** before the user has even typed a question. -The MCP team's [2026 roadmap](https://blog.modelcontextprotocol.io/posts/2026-mcp-roadmap/) explicitly calls out gateways, proxies, and observability as priority work. +**Tooltrim** (`tooltrim` on npm) sits in front of your MCP servers so your agent sees one smaller tool catalog instead of every definition from every server. -**Tooltrim** (`tooltrim` on npm) is a small, drop-in proxy that sits in front of N upstream MCP servers and: +*Disclaimer: not affiliated with Anthropic, the MCP spec authors, or any upstream MCP server project.* -- **filters** their tool / resource / prompt lists down to the ones your project actually uses, -- **shrinks** verbose tool descriptions and dedupes JSON-Schema sub-trees, deterministically, -- **traces** every JSON-RPC frame as NDJSON so you can finally see what your agent is doing, -- and **measures** how much context you saved. +--- -It speaks both `stdio` and `Streamable HTTP` in both directions, runs stateless behind a load balancer, and exports Prometheus + OpenTelemetry. +## ⚡ Features ---- +**`tooltrim`** — One MCP entry in your editor instead of five. Fans out to every upstream in your config. + +**`tooltrim measure`** — Wondering if this is worth it? Prints a before/after token table from your real config. + +**Glob filters** — `github.*` yes, `github.delete_*` no. Same rules on list *and* call, so denied tools stay denied. + +**Deterministic shrinker** — Trims bloated descriptions and dedupes JSON-Schema. Same input → same bytes, every time. -## The hero +**NDJSON tracing** — Every JSON-RPC frame logged. Finally see what your agent is actually doing. -Measured against five real, official `@modelcontextprotocol/*` servers (`server-everything` + `server-filesystem` + `server-memory` + `server-sequential-thinking` + `server-github`). The numbers below match the checked-in [`bench/REPORT.md`](bench/REPORT.md): full **`pnpm bench`** inside the **`tooltrim:dev` Docker image** (Debian bookworm, **linux-x64**, Node **20.18**), so CI and contributors get the same Linux-shaped baseline as the README—not a hand-tuned Windows-only run. +**Prometheus + OpenTelemetry** — Metrics, traces, and audit hooks for production deployments. + +And the numbers (5 official `@modelcontextprotocol/*` servers, reproducible in [`bench/REPORT.md`](bench/REPORT.md)): ```text -Scenario Tools Bytes Tokens vs raw (tokens) -all (raw) 63 49,505 10,401 −0.0% -all (shrunk) 63 45,276 9,590 −7.8% -common (filter+shrink) 17 14,488 3,084 −70.3% -task (filter+shrink) 3 3,165 656 −93.7% - -Proxy round-trip overhead +3.7 ms p50 / +6.7 ms p95 (tools/call, bench harness loopback inside container) -Throughput 50 concurrent calls, 0 errors, ~271 ops/sec -Agent (Claude Sonnet 4.5) ~77% fewer cumulative input tokens (direct vs Tooltrim task filter) — see report §5 +Scenario Tools Tokens vs raw +all (raw) 63 10,401 −0.0% +common (filter+shrink) 17 3,084 −70.3% +task (filter+shrink) 3 656 −93.7% ``` -Full reproducible report: [`bench/REPORT.md`](bench/REPORT.md). Run the harness on the host with `pnpm bench`, or in Docker (same numbers on Linux) — see [`docs/DOCKER.md`](docs/DOCKER.md) and [`bench/README.md`](bench/README.md). - --- -## Install and use +## Table of Contents + +- [⚡ Features](#-features) +- [📦 Installation](#-installation) +- [🧪 Usage](#-usage) +- [⚙️ Configuration](#️-configuration) +- [🛠️ Requirements](#️-requirements) +- [📁 Repo & Contributions](#-repo--contributions) +- [📄 License](#-license) -Published as [`tooltrim` on npm](https://www.npmjs.com/package/tooltrim). You do **not** need to clone this repository to run it. +⸻ -### Prerequisites +## 📦 Installation -- **Node.js 20+** ([nodejs.org](https://nodejs.org)) +### npx (recommended for editors) -### 1. Install (pick one) +No install. Point Cursor / Claude Desktop / Codex at: + +```json +{ + "mcpServers": { + "tooltrim": { + "command": "npx", + "args": ["-y", "tooltrim"] + } + } +} +``` -| How | Command | Best for | -|-----|---------|----------| -| **In your project** | `npm install tooltrim` | Copying `examples/` from `node_modules`, CI, apps that depend on Tooltrim | -| **Global CLI** | `npm install -g tooltrim` | Running `tooltrim` on your PATH anywhere | -| **No install** | *(skip)* — use `npx -y tooltrim …` below | Editors / MCP only; `npx` pulls the package from npm when needed | +Use your **project root** (folder with `tooltrim.config.yaml`) as the working directory. -Equivalent package managers: `pnpm add tooltrim`, `yarn add tooltrim`, or `pnpm dlx tooltrim --version` to confirm the registry. +### npm -### 2. Config file in your project root +```bash +npm install -g tooltrim # global CLI on your PATH +npm install tooltrim # local dep — copy examples from node_modules +``` -Tooltrim reads **`tooltrim.config.yaml`** from the current working directory (see [Config files](#config-files) for search paths). +Also: `pnpm add tooltrim`, `yarn add tooltrim`. -**If you used `npm install tooltrim` in that project**, copy the example and edit `servers:` for your MCPs: +### Docker ```bash -# macOS / Linux -cp node_modules/tooltrim/examples/tooltrim.config.yaml ./tooltrim.config.yaml +docker build -t tooltrim . ``` -```powershell -# Windows (PowerShell or cmd) -copy node_modules\tooltrim\examples\tooltrim.config.yaml .\tooltrim.config.yaml +See [`docs/DOCKER.md`](docs/DOCKER.md) for run flags and benchmark mounts. + +⸻ + +## 🧪 Usage + +### Start the proxy + +```bash +npx -y tooltrim +# or +tooltrim start --config tooltrim.config.yaml ``` -**If you never run `npm install`**, create `tooltrim.config.yaml` yourself using the [Configuration](#configuration) snippet below, or install once only to copy the example, then remove `tooltrim` from `package.json` if you prefer a clean tree and rely on `npx -y tooltrim` in the editor. +Reads `tooltrim.config.yaml` from the current directory (or pass `--config`). Fans out to all upstreams and exposes one merged MCP endpoint. -Check the file: +### Measure token savings ```bash -npx -y tooltrim validate-config +npx -y tooltrim measure --config tooltrim.config.yaml ``` -### 3. Point your MCP client at Tooltrim +Connects to your upstreams, lists tools raw vs filtered vs shrunk, and prints a token/byte table. This is how the README hero numbers are produced. -Use **`npx`** with **`-y`** so the IDE never blocks on an install prompt. +### Validate config -Cursor / Claude Desktop / Codex stdio config: +```bash +npx -y tooltrim validate-config +``` -```json -{ - "mcpServers": { - "tooltrim": { - "command": "npx", - "args": ["-y", "tooltrim"] - } - } -} +Parses and validates your config, redacts secrets, prints JSON. Run this before opening an issue. + +### Tail the trace log + +```bash +tooltrim trace tail +tooltrim trace tail --no-pretty # pipe to jq / Loki / Datadog ``` -The client should use the **project root** (folder that contains `tooltrim.config.yaml`) as the process working directory when it launches `npx`. +### Point your editor at Tooltrim + +1. Copy the example config: -### 4. What you get +```bash +cp node_modules/tooltrim/examples/tooltrim.config.yaml ./tooltrim.config.yaml +# or grab it from this repo: examples/tooltrim.config.yaml +``` -`tooltrim` fans out to every upstream in your config and exposes **one** merged, filtered, shrunk tool list to the client—replace per-server MCP entries with this single entry. +2. Edit `servers:` for your MCPs. +3. Add the `npx -y tooltrim` stdio entry above to your MCP client config. ---- +Replace per-server MCP entries with this single one. -## Configuration +⸻ -A complete example lives in [`examples/tooltrim.config.yaml`](examples/tooltrim.config.yaml). +## ⚙️ Configuration + +Minimal example — full reference in [`examples/tooltrim.config.yaml`](examples/tooltrim.config.yaml). ```yaml servers: @@ -122,137 +151,55 @@ servers: linear: transport: http url: https://mcp.linear.app/sse - auth: passthrough # forward inbound Authorization upstream - pg: - transport: stdio - command: ["uvx", "mcp-server-postgres", "--url", "${PG_URL}"] + auth: passthrough filters: - allow: ["github.*", "linear.create_*", "pg.query"] + allow: ["github.*", "linear.create_*"] deny: ["github.delete_*", "*.admin_*"] shrink: - mode: rules # rules | off | llm (v0.2) + mode: rules maxDescriptionChars: 160 dedupeSchemas: true - cachePath: .tooltrim/shrink-cache.json inbound: stdio: true http: enabled: true - host: 127.0.0.1 port: 8787 path: /mcp - sessions: stateless # stateless | redis://... (v0.2) observability: trace: { sink: file, path: .tooltrim/trace.ndjson } metrics: { prometheus: { enabled: true, port: 9464 } } - audit: { enabled: true, path: .tooltrim/audit.ndjson } ``` -`${VAR}` and `${VAR:-default}` are expanded from the environment in any string value. - -### Config files - -`tooltrim` searches for one of these, walking up from the cwd: - -- `tooltrim.config.yaml` / `.yml` -- `tooltrim.config.json` -- `tooltrim.config.js` / `.mjs` -- `.tooltrim.yaml` / `.yml` / `.json` -- a `"tooltrim"` key in `package.json` +**Filtering** — globs match the namespaced name `.`. Empty `allow` = everything; non-empty `allow` = gate; `deny` always wins. -You can also pass `--config ` to any command. +**Config search paths** — `tooltrim.config.yaml`, `.tooltrim.json`, `"tooltrim"` key in `package.json`, or `--config `. ---- - -## How filtering works - -Globs are evaluated against the **namespaced** name `.` using [micromatch](https://github.com/micromatch/micromatch). +`${VAR}` in any string is expanded from the environment. -- empty `allow` ⇒ everything is allowed, -- non-empty `allow` ⇒ only matches are allowed, -- `deny` is applied after `allow` and always wins. +⸻ -The same filter is enforced on `tools/list`, `resources/list`, `prompts/list`, **and** `tools/call`, so a denied tool can't be invoked even if the client cached its name. +## 🛠️ Requirements -## How shrinking works +- **Node.js 20+** +- **npx** (bundled with Node) for editor stdio transport +- Upstream MCP servers reachable via stdio spawn or HTTP URL -The default `rules` mode is fully deterministic: +⸻ -1. Strip Markdown decoration (headings, bold, italics, links, code fences). -2. Drop boilerplate prefixes (`"This tool ..."`, `"Use this when ..."`). -3. Strip mid-sentence boilerplate (`"Returns a JSON object containing ..."`). -4. Remove filler phrases (`"please"`, `"in order to"`, `"utilize"`). -5. Truncate at the first sentence boundary past `maxDescriptionChars`. -6. JSON-Schema dedup: any sub-tree that appears 2+ times is hoisted to `$defs` and replaced with `$ref`. +## 📁 Repo & Contributions -Output is hashed and cached in `.tooltrim/shrink-cache.json`, so the same description always shrinks to the same bytes — your agent never sees a moving target. - -The `llm` mode (v0.2) adds an optional offline pass that can be checked into git for reproducibility. - ---- - -## Observability - -### Tracing - -Every JSON-RPC frame in or out is one NDJSON line: - -```json -{"level":"info","time":"2026-05-02T22:00:00.000Z","trace":true,"dir":"out","upstream":"github","method":"tools/call","name":"github.create_issue","msg":"tools/call"} -{"level":"info","time":"2026-05-02T22:00:00.231Z","trace":true,"dir":"in","upstream":"github","method":"tools/call","name":"github.create_issue","ok":true,"durMs":231,"msg":"tools/call"} -``` - -```bash -tooltrim trace tail # follow with pretty-printing -tooltrim trace tail --no-pretty # raw NDJSON, pipe to jq / Loki / Datadog -``` - -### Metrics - -Prometheus endpoint at `http://:9464/metrics`: - -| metric | type | labels | -| --- | --- | --- | -| `tooltrim_calls_total` | counter | `upstream`, `tool`, `ok` | -| `tooltrim_call_duration_ms` | histogram | `upstream`, `tool`, `ok` | -| `tooltrim_tokens_saved` | gauge | `upstream` | -| `tooltrim_upstream_up` | gauge | `upstream` | - -A starter Grafana dashboard is in [`examples/grafana-dashboard.json`](examples/grafana-dashboard.json). - -### OpenTelemetry - -Set `OTEL_EXPORTER_OTLP_ENDPOINT` (or enable it in config) and Tooltrim initializes the Node SDK with an OTLP/HTTP trace exporter. - -### Audit - -Every `tools/call` lands in `.tooltrim/audit.ndjson` with the identity claims (`sub`, `iss`, `aud`, `scope`, `client_id`) decoded — but **not** verified — from the inbound `Authorization` Bearer token. Run a real auth gateway in front of Tooltrim if you need cryptographic verification. - ---- - -## CLI - -```text -tooltrim # start the proxy (default) -tooltrim start # explicit -tooltrim measure # before/after token report; the README hero -tooltrim validate-config # parse + validate, no startup -tooltrim validate-file # validate a JSON file against the schema -tooltrim trace tail # tail the NDJSON trace -``` - -All commands accept `-c, --config `. - ---- +🛠️ **Repo:** https://github.com/false200/Tooltrim +📦 **npm:** https://www.npmjs.com/package/tooltrim +📊 **Benchmarks:** [`bench/REPORT.md`](bench/REPORT.md) -## Contributing +PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports with `tooltrim validate-config` output are gold. -See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome — especially bug reports with a config that reproduces the issue. +⸻ -## License +## 📄 License [MIT](LICENSE)