Skip to main content
Glama

Any API → MCP

Experimental alpha software. This is an early, working prototype. APIs, file formats, CLI output and tool schemas may change without notice, and it is not production-ready. Try it, break it, and file issues.

Typed, token-efficient, navigable.

anyapi-to-mcp turns a REST API into something an agent can navigate rather than something it has to read. One declarative map per API describes its resources, the operations on them, the links between them, and how each response should be presented. An agent moves through that map instead of assembling URLs, headers and JSON envelopes — or loading a multi-megabyte OpenAPI document.

Status: alpha. The core works end to end: maps load, the view layer renders addresses and envelopes, the runtime executes operations under an outbound policy and stores payloads behind content-addressed handles, and the five verbs are served by both a stdio MCP server and the wrap2mcp CLI. A map can be imported from OpenAPI 3 or learned from one live call. The package is not yet published to npm.

Names

Thing

Name

Repository / npm package

anyapi-to-mcp

CLI

wrap2mcp

MCP server id in client config

wrap2mcp

Product

Any API → MCP

Both wrap2mcp and anyapi-to-mcp run the same CLI.

Related MCP server: liquid-mcp

Vocabulary

Term

Meaning

map

One declarative document per API: resources, operations, links, views, provenance.

resource

A thing the API exposes (customer, charge). Navigation is by resource.

operation

One callable request on a resource.

view

A declarative projection of a response.

link

A reference from one resource to another.

handle

An opaque id for a stored response payload.

address

A JSON Pointer into a stored payload or projection.

What it does

  • Navigate, not enumerate: ls, find, describe, run, read.

  • Keep discovery cheap: bounded listings, contract-on-demand, declared fields, capped output, one merged page.

  • Make drill-down free: responses are stored and addressed, so a follow-up costs no network call.

  • Speak one address space: machine output is compact; human output is an outline — and both print the same pointers.

  • Work as a standalone library and server, without Electron or a local bridge.

  • Import an OpenAPI spec, or learn a shape from one live call, and produce a map (“instant modernisation”).

  • Keep what matters: a pin names a query, re-resolves next session, and can be committed to the repository.

What it will do

  • Be published to npm so npx anyapi-to-mcp runs without a checkout.

  • Be embedded by a host app; the library, CLI and MCP server are all entry points into the same verbs.

The five verbs

Verb

Purpose

ls <path>

children, one line each (/pins lists saved queries)

describe <operation>

input contract, view, mutating flag, provenance

run <operation> k=v

execute; rows + meta + handle

find <query>

ranked search across the map

read <handle|@pin> <pointer>

drill into a stored payload, or re-resolve a pin

There is deliberately no cd: every address is absolute, so transcripts replay, subagents don’t inherit hidden state, and the audit log is reconstructable.

Usage

Run from this repository (pnpm cli builds once, then runs the compiled CLI); the tarball from pnpm pack installs into any project, and npm publication is still pending:

pnpm cli ls                                  # maps
pnpm cli ls /dummyjson                       # resources
pnpm cli describe products.list              # inputs, view, links
pnpm cli run products.list limit=3           # rows + handle
pnpm cli run products.list limit=3 --cursor c_…  # next page, from the cursor printed before
pnpm cli run products.add title=New --confirm  # a write: previewed first, then sent
pnpm cli run products.add title=New --writes open  # no confirmation for this call
pnpm cli read r_64172fe191255484 /products/1 # no network
pnpm cli find products
pnpm cli pin cheap products.list limit=3 --address /products --fields id,title,price
pnpm cli ls /pins                              # saved queries and their state
pnpm cli read @cheap                           # re-resolve, in any later session
pnpm cli unpin cheap
pnpm cli prune                                 # report; --apply removes dead pins
pnpm cli import openapi ./api.yaml             # spec -> map
pnpm cli learn https://dummyjson.com/products  # one call -> draft map
pnpm cli serve                                 # stdio MCP server

Add --workspace examples/workspace.json (or run from a directory containing workspace.json) to point at a set of maps. Commands that print an envelope also print a handle and an address; paste both back into read.

Handles are plain text in the OS cache and expire after 24 hours by default (payloads.ttlSeconds in the workspace changes it); a host that needs encryption or body redaction supplies its own PayloadStore.

A paginated response ends with a short cursor when the API offers a next page (offset totals and Link headers both work). Passing it back — run <operation> --cursor c_… — fetches that page through the same outbound policy, secret host check and view; the cursor stores only the operation and the next URL, never headers, in payloads/cursors. Cursors expire with the payload TTL. payloads.maxBytes caps the cache: the oldest records are evicted first, and the record just written is always kept.

Pins

A handle is cache: it expires, and it means nothing to the next session. A pin is a saved query — operation, arguments, address — so read @name runs it again and prints the same shape it would today:

pnpm cli pin cheap products.list limit=3 --address /products --fields id,title,price
# pinned cheap — products.list  [ok]
pnpm cli read @cheap
# @cheap — products.list → /products  [ok]
# {"summary":"3 rows, showing 3 (rows)","fields":["id:int","title:str","price:num"],…}

Pins live in one versioned JSON file named by the workspace (pins: { "path": … }), and each record carries its own state: ok, stale, broken:<code> (a selector miss, an HTTP error, an unbound secret) or retired (the operation is gone from every map). A failure marks the pin and keeps it; only retired and expired ephemeral pins are pruned, and prune is a report until --apply. A pin may not name a mutating operation, because reading it would repeat the write.

Writes

An operation that changes data — anything but GET/HEAD, declared or not — is run through the workspace's write policy. The default is confirm:

pnpm cli run products.add title=New price=9
# not sent: POST https://dummyjson.com/products/add  [mutating]
#   body: {"title":"New","price":9}
#   writes.mode is 'confirm'
# # nothing has been sent; repeat with confirm to send it

pnpm cli run products.add title=New price=9 --confirm
# wrote POST https://dummyjson.com/products/add · 201
# # document · 49B · r_4ee99d7fed2f2ace
# /  obj(3)

writes in workspace.json is a string or { "mode": … }:

Mode

Behaviour

confirm (default)

build the request, send nothing; repeat with --confirm / confirm: true

open

send it, after printing the wrote … line

deny

refuse with write_denied, even when confirmed — for a read-only deployment

The confirmation is a deliberate second step, not proof that a human agreed; a host that needs real approval puts one in front of the tool call. It never covers a pin: a pin may not name a mutating operation.

Two consequences worth knowing:

  • an optional body field the caller does not fill is omitted, so a partial PATCH sends only what was given, and a clean JSON body gets content-type: application/json;

  • a write that fails before or ambiguously after a response (timeout, network, 5xx) reports outcome_unknown, and is never retried automatically. Read the resource before retrying. A 204 prints as one line, no content.

Idempotency keys are not sent yet; see docs/DESIGN.md for the open items.

Instant modernisation

import turns an OpenAPI 3 document (JSON or YAML) into a map: resources from paths, operations with typed inputs and request templates, a first-draft view per operation, links from *_id fields, and the security scheme as a secret name — never a value.

pnpm cli import openapi ./openapi.yaml --out ./maps/my-api.json
# imported my-api — 5 resources, 19 operations, 1 link
#   base: https://api.example.com/v1
#   secrets to bind: api_key (host api.example.com) — a human writes the binding
#   notes: …array query parameters and multipart bodies are skipped, not guessed

learn is for APIs whose description is missing or wrong. It fetches the URL once, infers a shape from the JSON, drafts a view, stores the sample behind a handle and prints the proposed map; nothing is written unless --out or a writable map location exists.

pnpm cli learn "https://dummyjson.com/products?limit=3" --out ./maps/products.json

Corrections belong in overrides/<map>.json, not in the generated map: the importer will rewrite a generated file happily, but refuses to overwrite one that contains provenance: human entries unless you pass --force.

As an MCP server:

{
  "mcpServers": {
    "wrap2mcp": {
      "command": "node",
      "args": ["<repo>/bin/wrap2mcp.js", "serve", "--workspace", "<repo>/examples/workspace.json"]
    }
  }
}

As a library:

import { createClient, MemoryStore } from 'anyapi-to-mcp';

const client = createClient({ workspace: { maps: [{ path: 'maps' }] }, store: new MemoryStore() });

await client.ls('/');                       // what is here
await client.describe('products.list');     // what it needs
const result = await client.run('products.list', { limit: 3 });
const handle = (result.data as { handle: string }).handle;
await client.read(handle, '/products/1');   // no refetch

await client.pin({ name: 'cheap', operation: 'products.list', args: { limit: 3 }, fields: ['id', 'title', 'price'] });
await client.read('@cheap');                // re-resolves; name a pins path to persist it
await client.prune();                       // a report; { apply: true } removes dead pins

ls, find and describe are local. Nothing touches an API until run, and run --dry-run validates and builds the request without sending it.

Secrets

A map names a secret; it never holds a value, and no tool call can create a binding. A human writes the bindings file by hand — by default <config>/bindings.json, next to the maps — naming the one host the value may be sent to:

{
  "GITHUB_TOKEN": {
    "host": "api.github.com",
    "header": "authorization",
    "format": "Bearer {value}"
  }
}

host is required; header defaults to authorization and format to {value}. Then put the value in the environment (GITHUB_TOKEN=... in a POSIX shell, $env:GITHUB_TOKEN = "..." in PowerShell).

run --dry-run reports the header name and never the value; a secret bound to one host is refused for any other host, before any request is built.

Development

pnpm only, pinned through packageManager in package.json.

pnpm install
pnpm typecheck
pnpm test
pnpm build          # emits dist/ for the published artifact
pnpm pack           # the tarball ships dist, bin, README and LICENSE

pnpm cli builds first, because bin/wrap2mcp.js resolves the compiled dist/cli.js; the source itself is what the tests run.

Do not commit package-lock.json or yarn.lock. The MCP config above uses node plus a path because the package is not published yet; the published form will be npx -y anyapi-to-mcp serve.

Documentation

  • docs/DESIGN.md — the design notes: what is settled, and what is deliberately still open.

  • AGENTS.md — rules for anyone (human or agent) working here.

Not affiliated

Not affiliated with Anthropic, the Model Context Protocol project, or any API vendor. “MCP” refers to the open Model Context Protocol.

License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI agents to dynamically discover and interact with APIs through Swagger/OpenAPI specifications and Postman collections using a strategic four-tool approach. It streamlines API integration by providing universal tools for endpoint discovery, detailed request information, and authenticated execution.
    1
    -
  • A
    license
    A
    quality
    D
    maintenance
    Connect your agent to any HTTP API on the fly: Liquid discovers and maps any REST API once, then fetches typed data deterministically. Server-side search/aggregate, cross-API normalization, and structured recovery built in.
    6
    39 PyPI
    3
    AGPL 3.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to discover, search, and call any REST API described by an OpenAPI or Swagger document. Supports multiple API endpoints with authentication and parameter handling.
    10 npm
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Maps internal API endpoints to agent tools using OpenAPI specs, allowing agents to interact with internal services through natural language without per-agent tool wrappers.
    -