Metadata-Version: 2.5
Name: toolsmelt
Version: 0.8.0
Summary: Turn an API spec, a website, an app, a Postman collection or a database into curated, tested MCP tools, designed by an LLM.
Project-URL: Homepage, https://github.com/sepehr071/toolsmelt
Project-URL: Issues, https://github.com/sepehr071/toolsmelt/issues
Author-email: Sepehr Radmard <sepehrr80@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,code-generation,har,llm,mcp,model-context-protocol,openapi
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Code Generators
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: keyring>=25
Requires-Dist: mcp<3,>=2.2
Requires-Dist: pyyaml>=6
Requires-Dist: tomlkit>=0.12
Requires-Dist: truststore>=0.9
Requires-Dist: tzdata>=2024.1; sys_platform == 'win32'
Requires-Dist: websockets>=12
Provides-Extra: capture
Requires-Dist: playwright>=1.45; extra == 'capture'
Provides-Extra: grpc
Requires-Dist: grpcio-reflection>=1.60; extra == 'grpc'
Requires-Dist: grpcio>=1.60; extra == 'grpc'
Requires-Dist: protobuf>=4.25; extra == 'grpc'
Provides-Extra: sql
Requires-Dist: sqlalchemy>=2.0; extra == 'sql'
Provides-Extra: stealth
Requires-Dist: curl-cffi>=0.16.3; extra == 'stealth'
Requires-Dist: httpx-curl-cffi>=0.1.5; extra == 'stealth'
Description-Content-Type: text/markdown

<!-- mcp-name: io.github.sepehr071/toolsmelt -->

<p align="center"><img src="docs/assets/banner.png" alt="toolsmelt" width="100%"></p>

# toolsmelt

Formerly **mcpsmith**. `uvx mcpsmith` and servers generated by mcpsmith keep working; your saved connections in `~/.mcpsmith`, `MCPSMITH_*` variables and keychain entries are still read. Run `uvx toolsmelt setup` once to swap the agent entries over.

Turn an OpenAPI file, a Postman collection, a website you log in to, an app you own or a SQL database into a few curated tools for your AI agent. New formats plug in as packages ([docs/extending.md](docs/extending.md)). An LLM designs the tools; you get ordinary Python code you own if you want to publish them.

```bash
uvx toolsmelt setup
```

## What you can turn into tools

| Source | Command |
|---|---|
| OpenAPI / Swagger file or URL | `toolsmelt inspect <file-or-url>` |
| Postman collection (v2.0/v2.1 export) | `toolsmelt inspect shop.postman_collection.json` |
| cURL commands (DevTools "Copy as cURL") | `toolsmelt inspect requests.txt` |
| SQL database (SQLite, PostgreSQL, MySQL, anything SQLAlchemy reads) | `pip install "toolsmelt[sql]"`, then `toolsmelt inspect sqlite:///shop.db` |
| SOAP service (WSDL 1.1) or XML API | `toolsmelt inspect https://example.com/service.asmx?WSDL` |
| gRPC service (server reflection) | `pip install "toolsmelt[grpc]"`, then `toolsmelt inspect grpc://localhost:50051` |
| Website or logged-in dashboard | `toolsmelt browser`, then your agent records it (`toolsmelt capture <url>` records a HAR without the hub) |
| Android app (APK you own) | `toolsmelt app-android app.apk --base-url https://api.example.com` |
| Electron / JavaScript app | `toolsmelt app-js <dir\|app.asar\|file> --base-url https://api.example.com` |
| Desktop program | `toolsmelt app-desktop` (prints the mitmproxy steps), then `toolsmelt app-desktop capture.har --host api.example.com` |

Apps and programs: only software you own or are authorized to analyze.

<p align="center"><img src="docs/assets/screenshot-help.png" alt="toolsmelt command list" width="100%"></p>
<p align="center"><em><code>toolsmelt --help</code>: the command list.</em></p>

## Quickstart: a logged-in website

```bash
uvx toolsmelt setup                  # once: registers toolsmelt + Chrome DevTools MCP with your agents
uvx toolsmelt browser                # opens the shared Chrome; log in to the site yourself
# restart your agent, then ask it:
#   "Record example.com in the shared Chrome and connect it as tools."
# the extract-api skill runs the rest: record, inspect, design, validate, connect
```

Your passwords and 2FA codes never reach the agent. Details are under [Live hub](#live-hub).

## Proven on real software

- **Tusky 29.0** (open-source Android client, Retrofit): `toolsmelt app-android` found 113 endpoints with every HTTP method resolved (48 GET, 43 POST, 11 DELETE, 8 PUT, 3 PATCH) and no unresolved notes. Minified annotation names were resolved from the app's own request-builder class, not guessed.
- **GitHub Desktop 3.6.7** (Electron): `toolsmelt app-js` found 33 endpoints with every method resolved. All 33 were traced to their original TypeScript line through the app's source maps.
- **hn.algolia.com** (public API, no login): recorded in the shared Chrome. 2 curated tools were connected through the hub and returned live results. The tools were still present after restarting the hub.
- **An internal attendance dashboard** (ASP.NET, cookie login): recorded while logged in. 2 tools answered the owner's questions. After logout the tools return a clear session-expired error.

Numbers are from runs on 2026-10-21 with toolsmelt 0.4.0. Reproduce the first two with `toolsmelt app-android` and `toolsmelt app-js` on those apps.

<p align="center"><img src="docs/assets/screenshot-app-js.png" alt="toolsmelt app-js reading an Electron app's own code" width="100%"></p>
<p align="center"><em><code>toolsmelt app-js</code> reading an Electron app's own code.</em></p>

## For coding agents

No API key needed: your coding agent is the designer.

- Skills (plain Markdown, any agent can read them): [`skills/build-mcp-server`](skills/build-mcp-server/SKILL.md) (the whole workflow), [`skills/extract-api`](skills/extract-api/SKILL.md) (logged-in sites into live tools), [`skills/design-mcp-tools`](skills/design-mcp-tools/SKILL.md) (design checklist). [`AGENTS.md`](AGENTS.md) points Codex, Cursor and Gemini CLI at them.
- MCP tools (paths absolute): `smith_guide`, `smith_inspect`, `smith_app_android`, `smith_app_js`, `smith_app_desktop`, `smith_probe`, `smith_draft`, `smith_validate`, `smith_generate`, `smith_check`, `smith_install`, and for the hub `smith_record_start`, `smith_record_stop`, `smith_connect`, `smith_disconnect`, `smith_connections`, `smith_login`, `smith_tools`, `smith_call`.
- MCP config, any client:
  ```json
  {"mcpServers": {"toolsmelt": {"command": "uvx", "args": ["toolsmelt", "mcp"]}}}
  ```
- Claude Code plugin (skills + MCP server): `/plugin marketplace add sepehr071/toolsmelt`, then `/plugin install toolsmelt@toolsmelt`.

MCP clients start servers without your shell's proxy variables. Behind a proxy, add `"env": {"HTTPS_PROXY": "http://127.0.0.1:8080"}` so `smith_inspect` can fetch specs.

## Live hub

Skip the project: connect a plan and its tools appear in your agent right away. The toolsmelt MCP server is the hub. Connections are saved under `~/.toolsmelt` (`TOOLSMELT_HOME`) and load again on restart. They are personal (one machine, one user); `generate` stays the way to publish.

From the agent: `smith_inspect`, edit `plan.yaml`, `smith_validate`, then `smith_connect(workdir, name)` and call the new tools. If your client does not show tools added after start, use `smith_tools(connection)` and `smith_call(connection, tool, args)` (read-only tools only). `smith_connections` lists, `smith_disconnect(name)` removes (CLI: `toolsmelt connections`).

### Logged-in sites

For a dashboard or portal with no public API, the agent records the site's own calls while you use it. The `extract-api` skill runs the whole flow.

```bash
toolsmelt browser                                                      # shared Chrome on port 9222 (--port N, --no-proxy)
npx chrome-devtools-mcp@1.11.0 --browser-url=http://127.0.0.1:9222    # point Chrome DevTools MCP at it
```

1. Log in yourself in that Chrome window (passwords and 2FA never reach the agent).
2. The agent calls `smith_record_start(name, hosts)`, browses each feature twice with different inputs, then `smith_record_stop(name)`, which writes a HAR with cookies and tokens dropped.
3. It inspects, designs and connects the plan with `auth: {type: browser, login_path: /login}`. Calls reuse your Chrome session; when it expires the tool says to log in again.

Agents must not use DevTools `get_network_request`: it prints request headers, including cookies. `generate` refuses browser-auth plans because they only work with your Chrome; switch to token or API-key auth to publish.

### Websites without the hub

```bash
pip install "toolsmelt[capture]" && playwright install chromium
toolsmelt capture https://example.com -o site.har --host example.com   # browse, then close the tab
toolsmelt inspect site.har --host example.com
toolsmelt draft --name example-mcp
# redesign plan.yaml, then validate, generate, check as under Export & publish
```

While recording, do each thing you want as a tool: search, open a detail page, paginate, apply a filter. Anything you do not click is not in the capture. `--host` (repeatable) keeps only the hosts you list. A DevTools "Save all as HAR" export works in place of `capture`. The recording browser uses `HTTPS_PROXY` when it is set.

A HAR holds everything the browser sent and received, including cookies and your personal data: keep it out of git and delete it after `inspect`. `inspect` scrubs what it keeps: an API key or token becomes an `auth` env var with its value dropped, other secret-looking headers and query params are dropped, and emails, phone numbers, JWTs and values under secret or personal field names are masked in examples and test fixtures. This is pattern-based, so read `tests/fixtures/` before you publish a generated server. If a key is a public one the site sends to every visitor (a search or maps key), you may put it in the tool's `fixed` values.

### Apps and programs

Only for software you own or are authorized to analyze.

- Desktop program: `toolsmelt app-desktop` prints the steps to record its traffic with your own mitmproxy (`mitmdump --set hardump=capture.har`); `toolsmelt app-desktop capture.har --host api.example.com` imports the result. toolsmelt runs nothing for you, and certificate-pinned programs are out of scope.
- Android app: `toolsmelt app-android app.apk --base-url https://api.example.com` reads the app's Retrofit interface. It needs a decompiler: the free [`jadx`](https://github.com/skylot/jadx/releases) CLI on PATH (`scoop install extras/jadx`, `brew install jadx`), or REA's JADX jar (`REA_JADX_MCP_JAR`). Without `--class` it lists candidate classes. Before decompiling it triages the APK in about a second (zip listing and dex bytes): a Flutter or React Native app, or one without Retrofit, gets its next step right away (record its traffic, or `app-js` on a React Native JS bundle), and a known request-signing SDK names the sign-in provider to use.
- App that signs its own requests: plans use `auth: {type: signin, provider: xiaomi}` (Mi Home cloud is the first provider). The user signs in on the provider's own page through a link from the server's `<prefix>_login` tool (`smith_login` in the hub); the session goes to the OS keychain. Calling the login tool again names the account and where its data is, which catches a wrong account or region.
- Electron/JavaScript app: `toolsmelt app-js <dir|app.asar|file> --base-url ...` reads the calls in its shipped code.

Neither `app-android` nor `app-js` runs the app. The endpoints are unverified (no example response) until `toolsmelt probe` calls the GET ones live with your credentials (`--writes` for the rest, only after you agree). A method that cannot be read stays empty and `validate` refuses it.

## Setup

`uvx toolsmelt setup` registers the `toolsmelt` and `toolsmelt-chrome` (Chrome DevTools) MCP servers, and copies the skills into each coding agent it finds (Claude Code, Codex, Cursor, Gemini CLI). Options:

- `--dry-run` prints the plan and writes nothing.
- `--agents claude,codex` limits it to those agents (default: the ones found).
- `--rea` also installs REA (`rea-agents`). It is off by default: its MCP server adds about 200 tools to each agent, and toolsmelt's app path needs only plain tools such as `jadx`.
- `--no-skills` skips the skills; `--yes` skips the confirmation.
- `--remove` undoes what setup did. Entries and skill folders you made yourself are left alone. REA stays installed (you may have had it before); the command prints the line that removes it.

Before setup first changes a config file, it copies the file to `*.toolsmelt.bak`. The backup is made once and not refreshed by later runs. Restart your agents afterwards. Run `toolsmelt browser` before using Chrome DevTools: it starts the shared Chrome the server connects to.

Claude Code plugin users already get both servers from the plugin and do not need `setup`.

`toolsmelt install <project>` registers a server made by `generate` with the same agents, the same way: it runs the project folder (`uv run --directory`), or the released package with `--pypi`. It touches only the entry named after the plan and refuses one of that name that runs something else unless `--force`. `--remove` takes it out again.

<p align="center"><img src="docs/assets/screenshot-setup.png" alt="toolsmelt setup --dry-run plan" width="100%"></p>
<p align="center"><em><code>toolsmelt setup --dry-run</code> prints what it would change and writes nothing.</em></p>

## Transforms

Plans convert formats with `transform` on arguments, `output.transforms` (`date->unix`, `date->unix_end`, `unix->date`, `unix->datetime`, `minutes->hh:mm`, `toman->rial`, `rial->toman`, `int->str`, plus optional Jalali calendar conversions) and `timezone` (IANA name; empty means the system's local zone).

## Secrets

Credentials come from you, never the chat: `toolsmelt secret set <connection> <ENV_NAME>` asks with a hidden prompt and stores the value in the OS keychain (`--once` forgets it after one use, for one-time codes; `list` and `delete` also exist).

## Export & publish

To turn a plan into a standalone server project instead of a hub connection:

```bash
uvx toolsmelt inspect https://petstore3.swagger.io/api/v3/openapi.json   # .toolsmelt/source.json + digest.md
uvx toolsmelt draft --name petstore-mcp                                  # heuristic .toolsmelt/plan.yaml, no LLM
# redesign plan.yaml (your coding agent does this, or use `design`, see below)
uvx toolsmelt validate
uvx toolsmelt generate -o petstore-mcp
cd petstore-mcp && uv run pytest
uvx toolsmelt check .                                                    # call every tool live
uvx toolsmelt install .                                                  # register it with your agents
```

A spec read from a local file may only have a relative server URL; `validate` then asks you to set an absolute `base_url` in the plan.

### Let toolsmelt call an LLM

```bash
uvx toolsmelt design --goal "find products, compare prices, read reviews" --provider anthropic
```

Providers: `anthropic` (`ANTHROPIC_API_KEY`), `openai` (`OPENAI_API_KEY`), `openrouter` (`OPENROUTER_API_KEY`), `ollama` (no key). `--model` picks the model; `TOOLSMELT_MODEL` and `TOOLSMELT_BASE_URL` override the model and endpoint. The result is the same `plan.yaml`; review it before `generate`.

### How it works

```
openapi.yaml | site.har --inspect--> source.json + digest.md
                        --draft----> plan.yaml     (heuristic starting point)
   your agent edits plan.yaml, or `design` ------> plan.yaml
                        --validate-> issues
                        --generate-> <name>/       (server project)
                        --check----> live report per tool
```

`Source` is what the API offers; `Plan` is the server to build; code generation reads the plan plus captured responses for test fixtures. A plan, trimmed:

```yaml
name: shop-mcp
description: Search products and compare prices on Example Shop.
prefix: shop
base_url: https://example.com/api
env_prefix: SHOP_MCP
instructions: Search first, then fetch details with the id. Prices are in toman.
tools:
  - name: shop_search
    title: Search products
    description: Returns matching products with price and stock. Use shop_product for full details.
    endpoint: GET /v2/search
    args:
      - {name: query, type: string, required: true, description: "Search words, e.g. 'laptop'", to: query.q}
      - {name: max_price, type: integer, description: "Upper price limit in toman, e.g. 20000000", to: query.price_max}
      - {name: sort, description: "Result order.", map: {cheapest: price_asc, newest: date_desc}, default: cheapest, to: query.o}
    output:
      root: data.products
      fields: {id: id, title: title_fa, price: price.selling_price, in_stock: stock.available}
      max_items: 10
    example: {query: laptop}
```

Argument targets (`to:`): `path.<n>`, `query.<n>`, `header.<n>`, `cookie.<n>`, `body`, `body.<a.b.c>`. Output paths use dots, a number indexes a list (`images.0.url`), `[]` maps over a list (`tags[].name`). `map` turns friendly values the model sees into the cryptic ones the API wants.

A tool can also follow pages, or chain calls (one or the other per tool):

```yaml
    # in one tool: read up to 3 pages and join the lists at output.root
    paginate: {style: page, param: query.page, max_pages: 3}   # or offset, cursor, next_url
    # in another: search, then fetch the details of the first 5 hits
    steps:
      - {endpoint: getProduct, for_each: data.products, max_items: 5, args: {path.id: $item.id}}
```

`body_encoding` (`json`, `form` or `multipart`) says how `body.*` values are sent. A plan carries `schema_version: 1`, unknown keys are errors (a typo like `readonly:` fails instead of being ignored), and `toolsmelt schema` prints the plan's JSON Schema for editor completion and validation.

### What you get

```
<name>/
  pyproject.toml  server.json  README.md  LICENSE  .gitignore  toolsmelt.yaml
  src/<package>/server.py        # tools, built on toolsmelt.runtime
  tests/                         # replay captured responses; one -m live test per tool
  .github/workflows/             # ci.yml, publish.yml (PyPI + MCP Registry)
  .claude-plugin/plugin.json     # Claude Code plugin
  skills/<name>/SKILL.md         # skill for the generated server
```

`toolsmelt.yaml` is the plan, so you can regenerate. Each tool in `server.py` keeps its typed signature and calls `toolsmelt.runtime.interpret.run_tool`, the same code the hub runs.

Run a generated server (or the hub, `toolsmelt mcp`) over Streamable HTTP instead of stdio with `--transport http --port 8000` (or `MCP_TRANSPORT=http`); it listens on 127.0.0.1 at `/mcp`. Set `MCP_AUTH_TOKEN` to require `Authorization: Bearer <token>` on every request, or make it an OAuth 2.1 resource server for your identity provider with `MCP_OAUTH_ISSUER` (and optionally `MCP_OAUTH_AUDIENCE`, `MCP_OAUTH_SCOPES`, `MCP_PUBLIC_URL`): it validates the provider's JWTs (RS256/ES256, `iss`, `aud`, `exp`, scopes) and publishes `/.well-known/oauth-protected-resource/mcp`. Set one of them before you bind to a non-loopback `--host`. To publish: add a PyPI trusted publisher (workflow `publish.yml`, environment `pypi`), bump the version in `pyproject.toml` and `server.json`, tag `v*`.

### Databases

`toolsmelt inspect postgresql://reader@db.example.com/shop` reads tables, columns, keys and up to 3 sample rows (personal values masked). `draft` writes list and get-by-key tools as plain SQL (`sql:` with `:name` placeholders, args `to: sql.<name>`). A read-only tool must be one `SELECT` (or `WITH ... SELECT`); this is checked by `validate` and again at run time, and SQLite and PostgreSQL also open the connection read-only. The password never goes into the plan: it comes from `<P>_DB_PASSWORD` (or `toolsmelt secret set` in the hub). Install the driver for your database next to `toolsmelt[sql]` (for example `psycopg`).

### Tool-choice evals

`toolsmelt eval --write --provider anthropic` writes `evals.yaml` (one or two user questions per tool, plus a few that no tool should answer). Review the file and commit it. `toolsmelt eval --provider anthropic` then asks the model which tool and arguments answer each question, and reports tool and argument accuracy and which tools get confused. `--json` and `--min 0.9` make it a CI gate. The tool list is the cached prompt prefix, so repeat runs are cheap.

### Drift check

`toolsmelt check <project> [--json]` calls every tool with its example arguments and reports errors, output size in characters and output fields that came back empty. Run it nightly in CI to see when an API or site changes. It makes real calls, so tools marked `read_only: false` are skipped unless you pass `--writes`.

## Runtime features

Generated servers import `toolsmelt.runtime`, so a fix there reaches every server on upgrade.

- Retries on network errors, timeouts, 429 and 5xx (writes only on 429, so they are never sent twice); `Retry-After` honoured and capped; jitter; concurrency cap.
- GET response cache, host allowlist, 5 MB body cap.
- Proxy support: `<P>_PROXY` takes one URL or a comma list (pool). `<P>_BASE_URL` overrides the base URL. System proxy env vars are ignored on purpose.
- Chrome TLS fingerprint for sites that block plain clients: `pip install "toolsmelt[stealth]"` and `impersonate: true` in the plan. Without the extra it falls back to plain httpx with one warning.
- Compact output: `shape()` keeps only the planned fields, caps lists (reports the total count), clips long strings, drops nulls.
- Actionable errors: a 403 suggests setting `<P>_PROXY`, a 401 names the auth env var, 404/429/5xx say what to do next.
- Auth: bearer, header, query, cookie or basic, read from an env var.

## Comparison

Most generators mirror every endpoint as a tool and hand the model raw JSON. toolsmelt designs a few workflow-level tools with plain argument names, descriptions that say when to call what next, and compact output.

| | Input | Output | Tool design | Response shaping | Drift check | Open source |
|---|---|---|---|---|---|---|
| toolsmelt | OpenAPI, HAR, app code | live hub tools or Python MCP project | LLM-designed plan | per-tool root/fields/max_items | `toolsmelt check` | yes |
| FastMCP `from_openapi` | OpenAPI | runtime proxy | one tool per endpoint; manual route maps / transforms | raw JSON | no | yes |
| openapi-mcp-generator | OpenAPI | TypeScript server | one tool per endpoint; `x-mcp` to pick | raw JSON | no | yes |
| Speakeasy / Gram | OpenAPI | TypeScript or hosted | manual toolsets | experimental | no | yes (AGPL, since Sep 2026) |
| Cloudflare Forge / Code Mode | OpenAPI | SDK, CLI, MCP server | code mode (search + execute) | n/a | no | yes |
| AnythingMCP | OpenAPI, Postman, SOAP, GraphQL, SQL, HAR | self-hosted gateway | one tool per operation, visual editor | manual | no | yes (AGPL) |
| mitmproxy2swagger, har-to-openapi | HAR / traffic | OpenAPI spec | none | none | no | yes |
| Integuru, reverse-api-engineer | HAR / browsing | API client code, not MCP | none | none | no | yes |
| Supergood, Wire | site walkthrough | hosted API / MCP | managed | managed | vendor claims | no |

Compiled from each project's public docs in October 2026; check them before relying on a cell. Stainless, which generated MCP servers in code mode, joined Anthropic in May 2026 and closed new signups. FastMCP's own docs say curated servers clearly beat auto-converted ones.

## Extending

Add an input format, a value transform or a sign-in provider as your own installed package, without forking: toolsmelt finds them through the entry-point groups `toolsmelt.importers`, `toolsmelt.transforms` and `toolsmelt.signin`. Plugin importers use `toolsmelt.scrub`, the same privacy rules as the built-ins. See [docs/extending.md](docs/extending.md).

## Limitations

- No browser per call: JS challenges and captchas are out of scope. Sites that need them will fail at `check`.
- A tool is one request, a paginated request (`paginate`) or a chain of requests (`steps`, with `for_each` fan-out). Logic beyond that (branching, computed values) needs hand edits to the generated `server.py`, and regenerating overwrites them.
- The generated project is Python only. Servers speak stdio, or Streamable HTTP with `--transport http`; HTTP auth is a shared bearer token (`MCP_AUTH_TOKEN`) or OAuth tokens from your own identity provider (`MCP_OAUTH_ISSUER`); toolsmelt is not an authorization server.
- A HAR only contains what you did while recording; unrecorded features are not in the plan.
- Endpoints read from app code are unverified until `probe`; a method that cannot be read stays empty.
- Request bodies are JSON, form (`application/x-www-form-urlencoded`) or multipart text fields (`body_encoding` on a tool); file uploads are not supported.
- The plan is trusted input: its text ends up in server instructions and the skill, and `toolsmelt check` runs the generated code. Review a plan written by an LLM from third-party content before you generate and publish it.
- Alpha.

## Develop

```bash
uv sync
uv run pytest -q
uv run ruff check .
```

See [CONTRIBUTING.md](CONTRIBUTING.md) and [docs/design.md](docs/design.md).

## License

MIT
