Skip to main content
Glama
jorgeortega

openapi-agentify

openapi-agentify

Turn the API you already run into an MCP server: one middleware, from your OpenAPI spec, in-process.

CI npm License: MIT

Add one middleware to your Express, Hono, Fastify or Next.js app. It reads your OpenAPI spec (or drafts one from your routes) and serves /mcp with one tool per operation. A tool call becomes the HTTP request the operation describes and runs in-process through your own routes: your auth middleware, your validation and your handlers, the ones you already test. There is no second server, no proxy and no extra port.

It also serves what agents read before they call: llms.txt, an MCP server card, the AI Catalog, your spec with agent hints, and /agent, an index of all of them. And it scores how well a model will understand your spec, then suggests the fixes.

Quick start (30 seconds)

cd your-api
npx openapi-agentify init --write
npm install
npm run dev

init detects the framework and the file that creates your app, finds your spec (or drafts one from your routes), scores it, writes agentify.config.ts and agentify.tools.json (reads on, writes off), and adds two lines to your entry file. Then point any MCP client at http://localhost:3000/mcp:

npx @modelcontextprotocol/inspector   # Streamable HTTP, http://localhost:3000/mcp

Or by hand, on Express:

import express from "express";
import { agentify } from "openapi-agentify/express";
import { defineConfig } from "openapi-agentify";

const app = express();
app.use(agentify(defineConfig({ name: "Acme Orders API", spec: "./openapi.yaml" }))); // first, before body parsers and auth
app.use(express.json());
app.get("/v1/orders/:id", requireApiKey, getOrder); // unchanged

Related MCP server: Graft

Frameworks

Framework

Versions

Mount

Tool calls reach your app through

Express

4 and 5

app.use(agentify(config))

an in-memory IncomingMessage/ServerResponse pair, through app itself

Hono

4

app.use(agentify(config, { app }))

app.fetch(request, env), with your bindings

Fastify

5

await app.register(agentifyFastify, config)

app.inject()

Next.js (App Router)

15 and 16

agentifyNext(config, { routes }), a catch-all route and wrap()

the route module's exported GET/POST/…

Need a socket (TLS client certificates) or another language? dispatch: { mode: "http", url } sends tool calls over loopback HTTP instead.

How it fits

 npx openapi-agentify init   reads package.json, finds or drafts the spec,
                             scores agent readiness, writes agentify.config.ts
          │
          ▼
 your app (Express · Hono · Fastify · Next.js)
 ┌───────────────────────────────────────────────────────────────────────┐
 │ app.use(agentify(config))                                             │
 │   POST /mcp                          MCP (2026-07-28 and 2025-xx)     │
 │   GET  /openapi.json, /openapi.yaml  your spec, with agent hints      │
 │   GET  /llms.txt, /llms-full.txt     for models reading docs          │
 │   GET  /mcp/server-card              MCP server card (SEP-2127 draft) │
 │   GET  /.well-known/ai-catalog.json  where the card is                │
 │   GET  /agent                        one index of all of the above    │
 │ on your routes: agent detection · Idempotency-Key · your hooks        │
 │                                                                       │
 │   tool call ──in-process──▶ your existing route handler + auth        │
 └───────────────────────────────────────────────────────────────────────┘

What's included

  • MCP endpoint: Streamable HTTP, JSON responses, stateless; both protocol eras (2026-07-28 with server/discover and the Mcp-Method/Mcp-Name header rules, and 2025-11-25/06-18/03-26 with initialize). Tested against the official MCP TypeScript client in both.

  • In-process dispatch: path, query, header and JSON body parameters mapped from the spec; only declared parameters reach your app; credentials and X-Agentify-* headers can't be forged by the model; non-2xx answers become tool errors the model can read; timeouts and response caps.

  • Tools from your spec: OpenAPI 3.0 and 3.1 (JSON or YAML, a file, a URL or read from your running app), validated with JSON Schema 2020-12 in linear time, plus code tools (defineTool() with a Zod schema) for work that isn't a route. agentify.tools.json is the reviewable list: names, descriptions, which writes are enabled.

  • Discovery documents: llms.txt and llms-full.txt (llmstxt.org), the MCP server card (SEP-2127), the AI Catalog, /openapi.json and /openapi.yaml with x-agentify hints, /agent, Link: headers on /. Cached with ETags; each one can be switched off.

  • Agent readiness check: npx openapi-agentify check scores what a model will get wrong about your API, 0–100, over 13 weighted rules (operationIds, descriptions, parameter docs, response schemas, auth, examples, errors, pagination, idempotency, enums, tool count). improve writes rule-based fixes into agentify.tools.json (never your spec); --accept serves them.

  • A spec drafter: no spec? draft reads your Express, Hono (including zValidator and @hono/zod-openapi), Fastify or Next.js source with TypeScript's parser (your code is never run) and writes OpenAPI 3.1.

  • On your routes: forged X-Agentify-* headers stripped, agents detected (a dated User-Agent list and Web Bot Auth), Idempotency-Key honoured on agents' writes, paths a router could resolve differently refused.

  • Hooks for everything else: authenticate, beforeToolCall, afterToolCall, guardRoute, describe, extra surfaces. Bring your own API keys, OAuth, rate limits, metering or payments. See docs/extending.md.

The CLI

npx openapi-agentify init [dir]          # the whole setup (--yes, --write, --spec, --url, --include-writes…)
npx openapi-agentify check [spec|url]    # the readiness report (--json, --fail-under 80)
npx openapi-agentify improve [--accept]  # suggested fixes, into agentify.tools.json
npx openapi-agentify draft [dir]         # a draft OpenAPI 3.1 from your routes
npx openapi-agentify sync                # the spec changed: update tools, keep your edits

npx openapi-agentify check --fail-under 80 in CI keeps the score honest.

Configuration

// agentify.config.ts
import { defineConfig } from "openapi-agentify";

export default defineConfig({
  name: "Acme Orders API",
  description: "Look up, create and refund orders.",
  baseUrl: process.env.PUBLIC_URL,       // your canonical origin: set it in production
  spec: "./openapi.yaml",                // or a URL, or "app:/docs/json" (read from your app)
  tools: "./agentify.tools.json",
  auth: { mode: "passthrough" },         // agents send the credentials your API accepts
  anonymous: "allow",                    // | "deny"
  detect: { include: ["/v1/*"] },
  dispatch: { mode: "in-process" },      // | { mode: "http", url }
  idempotency: true,                     // | { ttlSeconds, store }
  trustProxy: false,                     // true behind one proxy you run (Vercel: true)
  hooks: [],                             // see docs/extending.md
});

Every option is typed and commented in src/config.ts.

Security notes

  • Your auth stays in charge. By default the MCP endpoint forwards the caller's own credentials and your API decides. Agentify refuses to start if dispatch: http would send them to another origin.

  • Set baseUrl in production. It names your origin in every document, and it's the only origin whose pages may call /mcp from a browser (plus allowedOrigins). Without it, browser requests are accepted only from localhost pages on a localhost Host, which stops DNS rebinding.

  • Mount it first, before body parsers and auth, so it can strip forged X-Agentify-* headers. It never parses a body for your routes.

  • Give id parameters a pattern (or an enum, a format): a route outside the spec that matches a template is then not taken for the operation.

Examples

examples/orders-api is the same small Orders API in Express, Hono, Fastify and Next.js, with its own API-key auth and a deliberately thin spec (it scores 54; improve --accept brings it to 96):

git clone https://github.com/jorgeortega/openapi-agentify && cd openapi-agentify
npm install
npm run demo:hono        # or demo:express, demo:fastify, demo:next
curl -s localhost:3000/llms.txt

For MCP clients that start a process instead of calling a URL, npm run demo:stdio serves the Express version over stdio: each message is relayed to its /mcp, through the same middleware, with the tool definitions improve --accept wrote (examples/orders-api/stdio.tools.json, readiness 96). The repo's Dockerfile runs it (docker build -t openapi-agentify-demo . && docker run -i --rm openapi-agentify-demo); it is how the demo is listed on Glama, and it is not part of the npm package.

Docs

  • Extending: the hooks, with a worked example

  • Dispatch: how a tool call reaches each framework

  • Surfaces: every discovery document and its standard

  • Readiness: the rules, improve, sync and draft

Hosted and paid

openapi-agentify is the open core, MIT-licensed, and stays that way. If you'd rather not run it yourself, or you need the operational parts:

  • Hosted MCP at latitude10.tech/platform/mcp: your OpenAPI document served as an MCP server, with sealed upstream credentials, analytics and sign-in through your identity provider.

  • The Latitude 10 Agentify kit (paid) builds on this package with its own agent keys, OAuth, per-agent rate limits, usage metering and x402 pay-per-call, all attached through the hooks in docs/extending.md.

Contributing

Issues and pull requests are welcome: see CONTRIBUTING.md. Security reports: SECURITY.md.

License

MIT © 2026 October Moon LLC

Available Tools

3 tools
getOrderGet an orderA
Read-onlyIdempotent

Gets one order by its id: customer email, items (sku, qty, priceCents), totalCents, status (open, paid or refunded) and createdAt. Returns the API's 404 when there's no such order; use listOrders to find ids. Calls need the Orders API key (x-api-key header; the demo's is demo-customer-key-0001); without it the tool returns the API's 401.

NameRequiredDescriptionDefault
idYesThe order's id, such as ord_1002, as listOrders returns it.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare the read-only/idempotent profile, so the description adds real value: required auth (x-api-key header), the concrete demo key, the exact 401 failure mode without it, and the 404 behavior for unknown ids. That is more than an agent could infer from annotations alone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences ordered by importance: what it returns, then failure modes, then auth. The demo API key is mildly incidental detail, but it is tied directly to the 401 sentence and earns its place for a sandbox environment.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, yet the description enumerates the return payload fields and status enum values, and covers both the 404 and 401 paths plus the auth prerequisite. An agent has everything needed to call this correctly and interpret failures.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema itself documents the id format (ord_1002, as listOrders returns it), so the description's mention of id adds no new syntax or constraints. Baseline 3 applies since the schema carries the parameter burden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and resource ('Gets one order by its id') and enumerates the returned fields (customer email, items, totalCents, status, createdAt), which makes it unmistakably distinct from the sibling listOrders that returns many orders.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent to listOrders when it needs to discover ids, and states the 404 outcome for missing orders. It stops short of a full when-to-use/when-not matrix, but the boundary with listOrders is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

getSalesReportSales reportA
Read-onlyIdempotent

Totals for paid orders only (open and refunded orders are left out): the number of orders, their sum in cents (currency USD), and per SKU how many paid orders include it. Takes no arguments. Returns { currency, orders, totalCents, bySku }. Calls need the Orders API key (x-api-key header; the demo's is demo-customer-key-0001); without it the tool returns the API's 401.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false, and openWorldHint=false. The description adds meaningful context beyond that: authentication via x-api-key, the demo key value, and the 401 failure mode without a key.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is a single tight paragraph that front-loads the scope and output fields, then adds the auth caveat. Every sentence carries useful information with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-argument report tool with no output schema, the description supplies the filter rules, exact return keys, and auth/error behavior. Nothing an agent needs in order to call this correctly appears to be missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero input parameters, and the description confirms 'Takes no arguments.' With no parameters to document, the baseline is 4; the description appropriately spends its space on return shape instead.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening clause states a specific computation over a specific resource — paid orders — and enumerates the three outputs. This clearly separates it from listOrders and getOrder, which retrieve individual orders rather than aggregate sales.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It specifies the inclusion scope explicitly: paid orders only, with open and refunded orders excluded. That gives clear context for when the result is appropriate, though it does not explicitly route the agent to or away from sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

listOrdersList ordersA
Read-onlyIdempotent

Lists orders, oldest first, optionally filtered by status, 20 per page unless limit says otherwise. Use it to find an order's id before calling getOrder. Returns { orders, nextCursor }; pass nextCursor back as cursor for the next page (null on the last one). Calls need the Orders API key (x-api-key header; the demo's is demo-customer-key-0001); without it the tool returns the API's 401.

NameRequiredDescriptionDefault
limitNoHow many orders to return, 1 to 100 (default 20).
cursorNoWhere to continue from: the cursor the previous page returned (nextCursor). Leave it out for the first page.
statusNoOnly orders with this status. One of: open, paid, refunded.

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed-world), yet the description still adds real value: default ordering, default page size of 20, the exact return shape, pagination termination (null cursor), and the auth requirement including 401 behavior. The only omission is what happens with an invalid cursor or status, which is minor.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with purpose and scope, then pagination mechanics, then auth. No restatement of the name or title and no filler; even the demo key serves a concrete calling need.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by spelling out the returned { orders, nextCursor } shape and how to consume it. Auth, defaults, and ordering are all covered, so an agent can call this correctly on the first attempt.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter meaning by tying cursor to the nextCursor return value and restating the 20-per-page default that limit overrides. It stops short of describing the status enum values, which the schema already covers.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (lists orders) plus the sort order and filter capability. It also explicitly distinguishes itself from the sibling getOrder by framing itself as the id-lookup step that precedes it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a concrete when-to-use rule: 'Use it to find an order's id before calling getOrder,' which tells the agent the prerequisite relationship with the sibling. Pagination usage is also prescribed (pass nextCursor back as cursor), leaving nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.1
    • Removedget2
    • AddedgetOrder
    • ChangedlistOrders3 fields changed
      • addedInput schema / properties / cursor / description
        Added value: +"Where to continue from: the cursor the previous page returned (nextCursor). Leave it out for the first page."
      • changedInput schema / properties / limit / description
        Previous value: -"Page size."New value: +"How many orders to return, 1 to 100 (default 20)."
      • addedInput schema / properties / status / description
        Added value: +"Only orders with this status. One of: open, paid, refunded."
  2. 3 tool updatesv0.1.0
    • First observedget2
    • First observedgetSalesReport
    • First observedlistOrders

TDQS

A4.4/5.0

Scored across 3 tools

Disambiguation5/5

The three tools have clearly distinct purposes: listOrders discovers and paginates orders, getOrder retrieves one order's details, and getSalesReport aggregates paid-order totals. The descriptions explicitly guide when to use listOrders before getOrder, leaving no meaningful overlap.

Naming Consistency5/5

All tools follow a predictable camelCase verb+noun pattern: listOrders, getOrder, getSalesReport. The singular/plural noun choices match the operation semantics.

Tool Count4/5

Three tools are a reasonable, well-scoped set for a minimal read-only orders/reporting interface. It is slightly thin, but each tool earns its place and there is no bloat.

Completeness3/5

The surface covers listing, single-order retrieval, and sales aggregation, but it lacks order creation, status updates, refunds, and deletion. For an Orders API domain, these are notable gaps that would leave agents unable to perform common lifecycle operations.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Exposes Express.js API endpoints as MCP tools, preserving existing schemas and authentication behavior. Supports streaming responses and can be mounted directly to existing Express apps or run as a standalone gateway.
    11 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables building agent-ready APIs that expose tools as both HTTP and MCP endpoints from a single server definition, with automatic OpenAPI, discovery docs, and interactive API reference.
    5
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to call existing Node.js API routes as MCP tools across NestJS, Express, Fastify, Koa, Hono, and AdonisJS, preserving existing auth, validation, and middleware.
    11 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Turns existing FastAPI routes (or any OpenAPI spec) into owner-approved MCP tools served from one governed endpoint, keeping tools hidden until an owner reviews a diff and enables them. Agents get incremental discovery via meta-tools, response field projection, per-caller API-key policy, PII redaction, and a JSON-lines audit log.
    160 PyPI
    1
    MIT