Skip to main content
Glama
moeealii
by moeealii

tool-forge

Turn any OpenAPI spec into a code-mode MCP server.

npx -y github:moeealii/tool-forge serve https://petstore3.swagger.io/api/v3/openapi.json

Most OpenAPI-to-MCP converters give the model one tool per endpoint. Point one at a real API and the model has to pick from hundreds of tools. Every intermediate response lands in its context, and a five-step task takes five round trips.

tool-forge gives the model a typed api object instead, and lets it write the glue:

const owner = "swamp-club", repo = "swamp";
const [info, releases, contributors] = await Promise.all([
  api.reposGet({ owner, repo }),
  api.reposListReleases({ owner, repo, per_page: 3 }),
  api.reposListContributors({ owner, repo, per_page: 5 }),
]);
return {
  stars: info.stargazers_count,
  latestReleases: releases.map((r) => r.tag_name),
  topContributors: contributors.map((c) => `${c.login} (${c.contributions})`),
};

Against GitHub's live API, that script made 3 calls in parallel in about half a second. The release data alone was over 52,000 characters of JSON. The model got back the ~400-character answer and nothing else.

Models write code more reliably than they juggle tool calls, and code can loop, branch, join and filter. tool-forge supplies the parts that make that safe against a real service.

How it works

The MCP server exposes one tool, execute. Its description holds TypeScript declarations generated from the spec: one method per operation, with argument and return types, doc comments, and whether it is read-only or destructive:

declare const api: {
  /**
   * Get a pet
   * GET /pets/{petId} · read-only
   */
  getPet(args: {
    petId: string;
  }): Promise<{
    id: string;
    name: string;
    nickname?: string | null;
  }>;
};

APIs too large to describe in full (GitHub's is 1.3 MB of TypeScript) also get search_api. It ranks operations with BM25 over their summaries, names, tags and paths, folds plurals and maps verb synonyms (add → create), and returns only the matching declarations. Return types are kept compact: the nesting is deep enough to write code against without paying for every field of every nested object.

The model's script runs in a QuickJS WebAssembly sandbox:

  • The only way out is api.*. There is no fetch, require, filesystem, process or timers, and api is frozen.

  • Every api call goes back to the host, where credentials, rate limiting, retries and the audit log apply. Secrets never enter the sandbox.

  • Each run has a heap limit (64 MB), a CPU deadline and wall-clock limit (60 s), and a call budget (50). Runaway loops are interrupted, and calls that never return time out.

  • A failed call throws ApiError with .status and .body, so the script can handle a 404 itself. Uncaught errors come back with line numbers that match the script.

  • console.log output is returned with the result. Large results are truncated with a hint to return less.

Related MCP server: yamcp

Safety, unchanged from before

  • Read-only by default. POST/PUT/PATCH/DELETE operations don't exist on api unless you pass --allow-writes. Scope further with --include/--exclude globs on operationId, tag or "METHOD /path".

  • Secrets stay in the environment. --auth bearer:GITHUB_TOKEN names a variable, never a value.

  • Polite under load. A shared token-bucket rate limiter, retries with jittered backoff on 429/502/503/504 that honour Retry-After, and per-request timeouts. A POST or PATCH is only retried on 429 or a refused connection, never on a 5xx where it may already have been applied.

  • Audit log. --audit calls.jsonl records every API call (secret query values redacted, argument names but not values) and every script run (duration, call count, outcome).

Quick start

tool-forge types ./openapi.yaml        # the TypeScript the model will see
tool-forge exec  ./openapi.yaml script.js   # run a script yourself, exactly as the model would
tool-forge list  ./openapi.yaml        # operations, access level, and what was skipped and why

Claude Code

claude mcp add github -e GITHUB_TOKEN=ghp_... -- \
  npx -y github:moeealii/tool-forge serve \
  https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json \
  --auth bearer:GITHUB_TOKEN --header "User-Agent:tool-forge"

Claude Desktop, Cursor and other clients

{
  "mcpServers": {
    "immich": {
      "command": "npx",
      "args": ["-y", "github:moeealii/tool-forge", "serve", "http://nas.example.com:2283/api/spec.json",
               "--base-url", "http://nas.example.com:2283/api", "--auth", "header:x-api-key:IMMICH_API_KEY",
               "--audit", "/var/log/immich-mcp.jsonl"],
      "env": { "IMMICH_API_KEY": "..." }
    }
  }
}

Options

Flag

Default

--base-url <url>

first servers entry

Relative server URLs resolve against the spec's URL

--auth <spec>

none

bearer:ENV, header:Name:ENV, query:name:ENV, basic:USER_ENV:PASS_ENV. Repeatable

--header <Name:Value>

Static header on every request. Repeatable

--allow-writes

off

Put state-changing operations on api

--include / --exclude <glob>

Repeatable

--max-calls <n>

50

API calls per script run

--exec-timeout <ms>

60000

Wall-clock limit per script run

--memory-mb <n>

64

Sandbox heap limit

--max-chars <n>

50000

Output returned to the model

--inline-types <n>

20000

Above this many characters of TypeScript, add search_api

--rps <n> / --burst <n>

5 / 10

Shared across all calls. --rps 0 disables limiting

--retries <n>

3

--timeout <ms>

30000

Per HTTP request

--audit <file>

JSON Lines

How a spec becomes an API

  • Path, query and header parameters become properties of args. The request body becomes args.body. Path-level parameters are inherited and operation-level ones override them.

  • $refs are inlined. Recursive schemas are cut off at the point of recursion.

  • OpenAPI-only keywords are translated or removed: nullable becomes | null, readOnly properties are dropped from request bodies, and Authorization/Cookie parameters are never exposed, because auth is injected by the host.

  • Query parameters follow the spec's style/explode. JSON, form-encoded and text bodies are supported. Operations that need multipart/form-data or binary uploads are listed as skipped, with the reason.

As a library

import { apiNames, buildTools, createServer, Executor, loadSpec, runInSandbox } from "tool-forge";

const spec = await loadSpec("./openapi.yaml");
const { tools } = buildTools(spec, { allowWrites: false, include: [], exclude: [] });
const executor = new Executor({ baseUrl: "https://api.example.com", auth: [], rps: 5, burst: 10,
  retries: 3, timeoutMs: 30_000, maxResponseChars: 50_000 });

const result = await runInSandbox("return (await api.listPets()).length;", apiNames(tools), executor);
const server = createServer(tools, executor, { name: "pets", version: "1.0.0" });

Limits

  • OpenAPI 3.0 and 3.1. Convert Swagger 2 first, for example with swagger2openapi.

  • Local $refs only. Bundle multi-file specs first, for example with redocly bundle.

  • stdio transport only.

Development

npm install
npm test     # 47 tests: spec handling, HTTP policy, type generation, sandbox isolation and limits,
             # and end-to-end runs where a real MCP client drives the server over stdio
npm run build

MIT licensed.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Turns OpenAPI specs into MCP tools with secure defaults, risk inspection, confirmation gates, response limits, audit logging, and secret redaction.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Convert any OpenAPI spec into a secure MCP server with scoped auth, per-tool allow/deny policies, rate limiting, and a redacted audit trail.
    10 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Converts any OpenAPI 3.x spec into a live MCP server, making every endpoint a validated tool that AI agents can call without writing glue code.
    5 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that exposes any OpenAPI REST API via two tools: search for discovering endpoints and execute for making API calls. It uses Code Mode to keep token footprint low and runs user-written JavaScript in a sandbox.
    6 npm
    3
    MIT