OpenAPI to MCP Server
mcp-use/mcp-use
Turns an OpenAPI or Swagger spec into an MCP server with the mcp-use TypeScript SDK, mapping each operation to a tool, wiring auth, testing and deploying.
Adding or changing API operations in @repo/operations. An agent skill from latitude-dev/latitude-llm.
$ npx skills add latitude-dev/latitude-llm --skill api-endpoints -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install latitude-dev/latitude-llm api-endpoints --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/latitude-dev/latitude-llm.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/api-endpoints .claude/skills/api-endpoints && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "api-endpoints" agent skill from https://github.com/latitude-dev/latitude-llm/tree/development/.agents/skills/api-endpoints into .claude/skills/api-endpoints/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "api-endpoints", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/latitude-dev/latitude-llm/tree/development/.agents/skills/api-endpointsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add latitude-dev/latitude-llm --skill api-endpoints -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install latitude-dev/latitude-llm api-endpoints --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/latitude-dev/latitude-llm.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.agents/skills/api-endpoints .agents/skills/api-endpoints && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "api-endpoints" agent skill from https://github.com/latitude-dev/latitude-llm/tree/development/.agents/skills/api-endpoints into .agents/skills/api-endpoints/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "api-endpoints", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add latitude-dev/latitude-llm --skill api-endpoints -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install latitude-dev/latitude-llm api-endpoints --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/latitude-dev/latitude-llm.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.agents/skills/api-endpoints .cursor/skills/api-endpoints && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "api-endpoints" agent skill from https://github.com/latitude-dev/latitude-llm/tree/development/.agents/skills/api-endpoints into .cursor/skills/api-endpoints/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "api-endpoints", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/latitude-dev/latitude-llm.git --path .agents/skills/api-endpoints--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add latitude-dev/latitude-llm --skill api-endpoints -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install latitude-dev/latitude-llm api-endpoints --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/latitude-dev/latitude-llm.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.agents/skills/api-endpoints .gemini/skills/api-endpoints && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "api-endpoints" agent skill from https://github.com/latitude-dev/latitude-llm/tree/development/.agents/skills/api-endpoints into .gemini/skills/api-endpoints/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "api-endpoints", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install latitude-dev/latitude-llm api-endpointsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add latitude-dev/latitude-llm --skill api-endpoints -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/latitude-dev/latitude-llm.git skills-src && mkdir -p .github/skills && cp -r skills-src/.agents/skills/api-endpoints .github/skills/api-endpoints && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "api-endpoints" agent skill from https://github.com/latitude-dev/latitude-llm/tree/development/.agents/skills/api-endpoints into .github/skills/api-endpoints/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "api-endpoints", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add latitude-dev/latitude-llm --skill api-endpoints -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install latitude-dev/latitude-llm api-endpoints --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/latitude-dev/latitude-llm.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.agents/skills/api-endpoints .opencode/skills/api-endpoints && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "api-endpoints" agent skill from https://github.com/latitude-dev/latitude-llm/tree/development/.agents/skills/api-endpoints into .opencode/skills/api-endpoints/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "api-endpoints", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
api-endpointsAdding or changing API operations in @repo/operations. An agent skill from latitude-dev/latitude-llm.
API Endpoints is an agent skill from latitude-dev/latitude-llm. Adding or changing API operations in @repo/operations. One source of truth (defineOperation + a Zod schema) becomes an HTTP endpoint, an OpenAPI operation, an MCP tool, TS + Python SDK methods, a latitude CLI command, and an in-process agent tool — descriptions and contracts must be written with all of these readers in mind.
Its SKILL.md is about 6.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Backend & APIs, covering REST APIs, OpenAPI specifications and MCP servers. It works with OpenAPI, Python and Zod. The repository describes itself as: Open-source observability for AI agents. Find where your agents fail, dispatch your coding agent to fix it, and verify the fix against real traces. The licence is MIT.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 87e8aa0. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
pnpmgituvnpmcargoFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
modelcontextprotocol.ioFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
API Endpoints loads about 6.5k tokens when it runs. Until then it costs about 87 tokens; SKILL.md has 2,606 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from latitude-dev/latitude-llm at commit 87e8aa0, republished under its MIT licence (© latitude-dev). 2,606 words, ~6,452 tokens.
.claude/skills/api-endpoints/SKILL.md (or your agent's skills folder).When to use: Adding a new operation to the public API, changing an existing one, or wondering why mcp.json / openapi.json / the SDK aren't in sync.
When you add a new operation, check whether the same action or read is already available in the web UI. The goal isn't full surface parity, it's not duplicating logic that the web already implements.
For each new operation, open apps/web/src/domains/<entity>/<entity>.functions.ts. Three cases:
*UseCase from @domain/*): reuse that use-case in the operation. Don't reimplement the logic.getBetterAuth().api.*: the API process can't reach the same in-process instance. Write a domain use-case that replicates that behavior (carefully — read the third-party source so your use-case matches its rules), then point both the web and the operation at the use-case. Adds parity tests so the migration doesn't silently drift.The domain use-case is the shared seam between web and API. Duplicating logic in both surfaces creates drift — one gets a bug fix the other doesn't.
If the entity doesn't have a .functions.ts because the UI doesn't expose this action yet, you're designing fresh. That's fine; just don't lose the option to share later — put the business logic in a @domain/* use-case from the start rather than inline in the operation.
Every operation in packages/operations is one declaration that fans out into every generated surface:
| Surface | Generated from | Consumed by |
|---|---|---|
| HTTP route (Hono) | route.method + route.path + execute/handler | curl, internal services — mounted by apps/api |
| OpenAPI operation | route.name (→ operationId), route.description, request/response schemas | apps/api/openapi.json — the source Fern reads for the SDKs + CLI |
| MCP tool | route.name, route.description, flattened input + 2xx-JSON output schema | apps/api/mcp.json, runtime /v1/mcp transport |
| TS + Python SDK methods | Fern reads openapi.json; group/sdkMethod name the method | end-user TypeScript (@latitude-data/sdk) and Python (latitude-sdk) code |
latitude CLI command | Fern reads openapi.json | shell users + AI agents (latitude <resource> <verb>, --help, --schema) |
| In-process agent tool | defineToolset({ groups }) selection over execute-form operations | internal AI agents (e.g. the signal-creation agent) — no HTTP, no tokens |
You don't write these configs separately. You write one. The machinery in packages/operations/src/core/* derives the MCP tool and agent tools, and Fern derives the SDKs + CLI from openapi.json.
This means: the descriptions you put on routes and on schema fields are read by SDK users, by AI agents calling the MCP or an internal toolset, AND by CLI users (as --help/--schema text). Treat every description as user-facing copy. Vague or absent descriptions are bugs.
packages/operations/src/operations/<resource>.tsPrefer execute-form (execute + typedResponses) for new operations — it's transport-neutral, so the operation is eligible for in-process agent toolsets and gets a typed status/body union. handler-form (a raw Hono handler + openApiResponses) still exists on unconverted modules; convert opportunistically when touching one.
import { createRoute, z } from "@hono/zod-openapi"
import { Effect } from "effect"
import { defineOperation } from "../core/define-operation.ts"
import type { OperationModule } from "../core/mount.ts"
import { jsonBody, PROTECTED_SECURITY, typedResponses } from "../openapi/schemas.ts"
import type { OrganizationScopedEnv } from "../types.ts"
// Step 1: declare the mount path once; the module carries it.
const widgetsPath = "/widgets"
// Step 2: bind the operation factory to the Env type AND the path.
const widgetOperation = defineOperation<OrganizationScopedEnv>(widgetsPath)
// Step 3: define the boundary schemas. EVERY field gets `.describe(...)` if its
// purpose isn't obvious from the name. Descriptions land in `openapi.json`
// (SDK docstrings), `mcp.json` (MCP tools), and agent toolsets.
const WidgetSchema = z
.object({
id: z.string().describe("Stable identifier; safe to use as a primary key in client storage."),
name: z.string().describe("Human-readable label, unique within an organization."),
createdAt: z.string().describe("ISO-8601 timestamp of creation."),
})
.openapi("Widget") // ← registers a named OpenAPI component (needed by Fern). Different from `.openapi({ description })`.
const CreateWidgetBody = z
.object({
name: z.string().min(1).describe("Display name for the new widget. Must be non-empty."),
})
.openapi("CreateWidgetBody")
// Step 4: declare each operation.
const createWidget = widgetOperation({
route: createRoute({
method: "post",
path: "/",
name: "createWidget", // ← camelCase. Becomes OpenAPI `operationId` AND MCP tool name.
tags: ["Widgets"],
group: "widgets", // ← SDK group (x-fern-sdk-group-name) AND agent-toolset selector. Declare right after `tags`.
sdkMethod: "create", // ← SDK method name inside the group (x-fern-sdk-method-name).
summary: "Create widget", // ← short label; falls through to MCP tool `title`.
description: "Creates a widget in the caller's organization. Returns the persisted record.",
security: PROTECTED_SECURITY,
request: { body: jsonBody(CreateWidgetBody) },
responses: typedResponses({ status: 201, schema: WidgetSchema, description: "Widget created" }),
}),
access: "write", // ← required: "read-only" | "write" | "destructive". See "Access" below.
rateLimitTier: "low", // ← declarative; apps/api maps it to middleware at mount time.
execute: (input, ctx) =>
Effect.gen(function* () {
// input.body / input.params / input.query are pre-validated; ctx carries
// organization, auth, and clients. Pipe your layers exactly as before:
// .pipe(withPostgres(..., ctx.postgresClient, ctx.organization.id), withTracing)
const widget = yield* createWidgetUseCase({ name: input.body.name })
return { status: 201, body: toResponse(widget) } as const
}).pipe(/* withPostgres(...), withTracing */),
})
// Step 5: export the module — mount order within `operations` is meaningful
// (Hono matches static-before-param routes in registration order).
export const widgetsModule: OperationModule = {
path: widgetsPath,
operations: [createWidget],
}Execute-form notes:
typedResponses is the literal-keyed twin of openApiResponses (identical runtime output). It makes the { status, body } union checkable: declared non-2xx variants (400/401/404) are returnable values; domain failures left in the Effect error channel re-throw and reach honoErrorHandler exactly as in handler-form. Keep its response keys literal — deriving them from a generic collapses the inference under tsgo (see the comment on typedResponses).ctx (OperationContext) carries organization, auth, and the platform clients — everything the old c.var provided. Tenancy is baked in: the organization is resolved before execute runs and is never part of the input.input contains only the sections the route declares (params / query / body), already validated.packages/operations/src/operations/index.tsAdd the import and one entry to operationModules. Position = mount position: openapi.json path order and mcp.json tool order derive from it, so append new modules at the end unless there's a routing reason not to.
pnpm openapi:emit # rewrites apps/api/openapi.json
pnpm mcp:emit # rewrites apps/api/mcp.jsonBoth files are checked in. CI guards against drift, so commit them alongside the module.
The TS + Python SDKs and the latitude CLI all regenerate from openapi.json via Fern — pnpm generate:sdk (both SDKs), pnpm generate:cli (CLI), or pnpm generate:all (all three; needs Docker + a Rust toolchain for the CLI). CI (api-manifests.yml) regenerates all of them and fails on drift, so run pnpm generate:all and commit the results when your PR changes the surface — otherwise the drift check goes red.
Always use these pnpm scripts — never run fern generate directly. The generate:sdk:* scripts pin --version (from the SDK's package.json / pyproject.toml) so the version Fern bakes into the generated Python client_wrapper.py User-Agent is deterministic; a bare fern generate omits it and re-stamps a registry-derived version ("last published + a patch"), which flaps the drift check on every SDK publish. See fern/README.md.
Publishing the regenerated surface is version-gated per package — and the CLI is the easy one to miss. Regeneration only rewrites the generated source; each package publishes on push to development only when its version advances, and the three track it differently. The SDKs read a manifest: bump version in packages/sdk/typescript/package.json (TS → npm; publishes when it differs from npm view) and in packages/sdk/python/pyproject.toml (Python → PyPI; publishes when the version isn't on PyPI). After bumping the Python version, run uv lock in packages/sdk/python and commit uv.lock — the publish action runs uv lock --check and a manifest bumped without a re-lock fails the publish. The CLI has no manifest to bump — its version is the top ## [X.Y.Z] entry in packages/cli/CHANGELOG.md (its Cargo.toml ships 0.0.0, patched at build via cargo set-version), and publish-cli.yml no-ops unless that top version has no cli-<version> release yet. Since new operations add SDK methods and CLI commands, whenever you bump the SDK versions for new surface, add a matching new ## [X.Y.Z] entry to packages/cli/CHANGELOG.md in the same change — otherwise the CLI regenerates with the new commands but never ships.
apps/api/src/routes/<resource>.test.ts — they stay in apps/api on purpose, testing through registerRoutes + app.fetch() so the full middleware chain (auth, org context, rate limiting, error mapping) runs end-to-end.apps/api/src/mcp/server.test.ts. Add a case there if the operation exposes behavior worth pinning at the MCP layer too.packages/operations/src/core/*.test.ts.packages/operations/src/toolsets/__snapshots__/*.manifest.json) will change — review and commit the diff deliberately; that diff IS the "this operation now flows to an agent" review surface.Every field in every request/response schema needs a description unless the field name is self-explanatory. Descriptions reach four distinct audiences:
@param / property comments).inputSchema / outputSchema — and internal agents read the same schemas through toolsets — to decide what to put in a tool call.--help text and machine-readable --schema output on the generated latitude commands.Write each description as one short sentence in present tense, like a microcopy label. Examples:
// Good — tells the agent what the value is FOR
name: z.string().describe("Human-readable label, unique within an organization."),
nextCursor: z
.string()
.nullable()
.describe("Opaque cursor for the next page. `null` when there are no more pages."),
// Not great — restates the field name
name: z.string().describe("The name."),
// Bad — no description at all on a non-obvious field
filters: filterSetSchema, // ← what shape? what semantics? agent has to guess..describe() vs .meta() vs .openapi()| API | When to use |
|---|---|
.describe("…") | Default for field-level descriptions. Sugar for .meta({ description }). Visible to OpenAPI AND MCP. |
.meta({ description, examples, default, ... }) | Equivalent to .describe() plus JSON-Schema-standard fields (examples, default, title). Visible to both surfaces. |
.openapi("Name") | Schema-component registration only — gives the schema a name under components.schemas in OpenAPI. Required for Fern to emit reusable types. Has nothing to do with descriptions. |
.openapi({ description, format, example, ... }) | OpenAPI-only metadata — format, example, param: { in, name }, etc. Lives in the openapi-extension WeakMap and does not propagate to MCP. Avoid for descriptions; use only for things that have no Zod-native equivalent. |
TL;DR: prefer .describe() / .meta(). Use .openapi("Name") to register named schema components. Reach for .openapi({...}) for fields ONLY when you need an OpenAPI-only knob like format: "uri".
If you find yourself writing .openapi({ description }), replace it with .describe() — descriptions hidden in the openapi WeakMap are invisible to MCP clients, which silently degrades agent UX.
User-facing descriptions (route description, schema .describe(), response description) are read by SDK users and AI agents. They aren't release notes for our backend. Keep them about the contract, not how we implement it.
Concretely, avoid:
description:.Examples:
// Bad — leaks soft-delete + retention behavior of an unrelated entity
description: "Soft-deletes a project by slug. Traces remain in storage but the project no longer appears in lists."
// Good
description: "Deletes a project by slug."
// Bad — describes the mechanism
description: "Revokes an API key by setting deletedAt and busting the Redis cache."
// Good
description: "Revokes an API key."Same rule for the verbs used in route/operation summary: "Delete project" beats "Soft-delete project".
rateLimitTier is a required-in-practice field on the operation args: mountOperationModules throws at mount time on a missing tier, and the emit scripts boot the same assembly, so an undeclared tier can't ship. apps/api maps the tier to createTierRateLimiter(tier) middleware attached to this exact (method, path) pair only, keyed on the authenticated org id (not IP), so one tenant's traffic doesn't eat another's quota and a stricter tier on DELETE /:id doesn't fire on GET /:id.
Default to low. Most CRUD operations don't need more — low is 1,000 req/min/org, which comfortably covers SDK polling, MCP tool calls, and human-driven dashboards. Step up only when the operation genuinely warrants tighter limits.
| Tier | Quota (per org / min) | Pick this when… |
|---|---|---|
low | 1,000 | The default: id-keyed CRUD, list of bounded size, simple lookups, account/settings reads. Most operations land here. |
medium | 600 | Mutations with non-trivial side effects, and moderate analytics reads. |
high | 150 | Bulk reads with filter / search / semantic / vector load that scan large data sets per request. |
ultra | 30 | Workflow-kicking ops: imports, exports, monitor-signal, anything that sends email or enqueues a heavy job. |
max | 10 | Unauthenticated or abuse-prone surfaces (used with extra global limiting; see routes/bootstrap.ts). |
Don't be harsh. A tighter tier doesn't make the API safer in any meaningful way for cheap operations — it just frustrates legitimate callers. When in doubt, pick low and bump it later if a specific operation shows up in incident traffic.
name is camelCase, verb-first, and reads like an SDK method: createApiKey, listProjects, assignSavedSearch. Avoid resource-prefixed names that read awkwardly as SDK calls (apiKeysList → use listApiKeys).group / sdkMethod name the Fern SDK surface (client.<group>.<sdkMethod>()) and are renamed in place to the x-fern-* extensions — declare them right after tags (emitted key order follows declaration order, and the checked-in openapi.json is diffed byte-for-byte in CI). group doubles as the agent-toolset selector.description on the route is the single-line tool/method blurb. Treat it as the first sentence an SDK user or AI agent sees when discovering the operation.summary is optional, shorter, and becomes the MCP tool title. Falls back to name when omitted.Every operation must declare an access field on its defineOperation args (a sibling of rateLimitTier, not inside createRoute). It's a single value — "read-only" | "write" | "destructive" — and TypeScript won't let you define an operation without it. access is the authoring surface for two things: the MCP tool annotations (translated to the spec's ToolAnnotations readOnlyHint/destructiveHint by accessToAnnotations, the single translation point) and the agent-toolset access ceiling (below).
const deleteWidget = widgetOperation({
route: createRoute({ /* method, path, name, group, sdkMethod, … */ }),
access: "destructive",
rateLimitTier: "medium",
execute: (input, ctx) => /* … */,
})Write vs destructive. Mirrors the MCP spec's additive/destructive split: a write is destructive if it can delete or overwrite an existing value (the prior value is lost), and just "write" if it only adds without touching what's there. An in-place update* is "destructive" even though it isn't a delete — overwriting a stored name/settings/cell replaces the previous value. When unsure about a non-additive write, prefer "destructive".
| Value | Wire annotations | Use for |
|---|---|---|
"read-only" | { readOnlyHint: true, destructiveHint: false } | Pure reads: GET lists, gets, analytics, histograms — even when the request uses POST to carry a complex query body. |
"write" | { readOnlyHint: false, destructiveHint: false } | Additive or reversible: create*/insert*/add*/invite*/import*, and state toggles (mute*/unmute*, monitor*/unmonitor*, resolve*, restore*, reorder*). Exports that enqueue a job or send email are "write" (they're not read-only). |
"destructive" | { readOnlyHint: false, destructiveHint: true } | delete*/revoke*/remove*, and in-place update* that overwrites stored values. |
Set access to match what the implementation actually does — a wrong value misstates risk to MCP clients and is load-bearing for agent toolsets (defineToolset refuses operations above its access ceiling, so an over-broad access can leak a mutation to an agent and a too-narrow one blocks a legitimate read). access is stripped before the route reaches the OpenAPI generator, so it surfaces only in mcp.json and the live MCP transport, never in openapi.json.
defineToolset (in packages/operations/src/core/toolset.ts) selects operations and shapes them as in-process tools (invoke(rawFlatInput, ctx) — validate, split, execute; no HTTP, no tokens). Selection is a filter: an operation is included when it's tool-eligible, execute-form (invocable in-process), within the access ceiling, and in spec.groups if given. Everything else is silently dropped — so an op joins a toolset automatically once it's converted to execute-form, and writes never reach a read-only toolset by construction. Only genuine misconfiguration throws: a groups entry matching no operation (typo), or a stale exclude. Concrete toolsets live in packages/operations/src/toolsets/ (they can't live in @domain/* — that would create a package cycle, since @repo/operations imports domain packages).
Access ceiling. ToolsetSpec.access (default "read-only") is the highest access the toolset admits, and it's cumulative: a "write" ceiling admits read-only and write operations; "destructive" admits everything. Operations above the ceiling are filtered out, so an agent gets no mutations unless the toolset explicitly opts up; the default keeps research agents read-only. groups is optional — omit it to span the whole registry, e.g. the shipped readOnlyToolset:
export const readOnlyToolset = defineToolset({ name: "read-only", access: "read-only" }, operationModules)
// or scope + raise the ceiling:
defineToolset({ name: "signal-writer", groups: ["signals"], access: "write" }, operationModules)Tenancy note for toolset consumers: build OperationContext from an already-resolved organization; the model-visible input never carries org identity.
Some operations shouldn't be tools — they make sense for HTTP/SDK clients but not for AI agents (e.g. internal lifecycle endpoints, web-only callbacks). Pass tool: false:
const internalReindex = widgetOperation({
route: createRoute({ ... }),
handler: async (c) => { ... },
tool: false, // ← HTTP route is mounted, MCP tool is skipped
})Run before opening the PR:
pnpm --filter @repo/operations typecheck && pnpm --filter @repo/operations test
pnpm --filter @app/api typecheck
pnpm --filter @app/api test
pnpm openapi:emit && git diff --exit-code apps/api/openapi.json # no drift
pnpm mcp:emit && git diff --exit-code apps/api/mcp.json # no driftSpot-check both manifests by hand: open apps/api/mcp.json and apps/api/openapi.json, find your operation, confirm every field has a description. If something is missing, it'll silently degrade SDK docs and agent UX — fix it at the Zod schema, not in the JSON output. In mcp.json, also confirm your tool's annotations (translated from access) match what the implementation actually does (see "Access" above).
If you need to debug the auto-generation pipeline:
packages/operations/src/core/define-operation.ts — defineOperation factory; baked-in prefix, dual execute/handler form, in-place group/sdkMethod → x-fern rename, mountHttp registers with the MCP registry on tool-eligible mounts.packages/operations/src/core/execute.ts — OperationInput/OperationOutput inference and the generated Hono handler around execute.packages/operations/src/core/mount.ts — mountOperationModules; applies rateLimitTier middleware, throws on missing tiers.packages/operations/src/core/registry.ts — module-global operation registry; collectToolDescriptors() emits the snapshot used by both the runtime MCP transport and mcp:emit.packages/operations/src/core/toolset.ts / core/invoke.ts — agent toolset selection + in-process invocation.packages/operations/src/operations/index.ts — the ordered module manifest (mount order lives here).apps/api/src/mcp/server.ts — per-request MCP server, dispatches each tool call back through rootApp.fetch() so the full middleware chain (auth, rate-limit, org-context, validation) re-runs on every inner call.apps/api/scripts/emit-openapi.ts / apps/api/scripts/emit-mcp.ts — boot the route registry with stub clients and serialize the manifests.packages/operations/src/openapi/schemas.ts and openapi/pagination.ts — shared boundary primitives (security scheme, typedResponses, Paginated(...), common param schemas).c.var.auth / c.var.organization get populated on protected routes.setupTestApi for HTTP-level integration tests.© latitude-dev, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .agents/skills/api-endpoints of latitude-dev/latitude-llm.
Open the folder on GitHubat commit 87e8aa0
API Endpoints next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| API Endpoints this skilllatitude-dev/latitude-llm | 4.7k | — | ~6.5k | Automated safety check: Pass | MIT | |
| OpenAPI to MCP Servermcp-use/mcp-use | 11k | — | ~5.2k | Automated safety check: Pass | Apache-2.0 | |
| API Endpoint Contracttrycompai/comp | 2k | — | ~2.7k | Automated safety check: Pass | AGPL-3.0 | |
| Kingdee MCP DevWaHaiLong/KingdeeMCP | 103 | — | ~853 | Automated safety check: Pass | MIT | |
| Ns APINethServer/nethsecurity | 191 | — | ~2.8k | Automated safety check: Pass | Custom licence | |
| FastAPI ExpertJeffallan/claude-skills | 12k | — | ~1.8k | Automated safety check: Pass | MIT |
mcp-use/mcp-use
Turns an OpenAPI or Swagger spec into an MCP server with the mcp-use TypeScript SDK, mapping each operation to a tool, wiring auth, testing and deploying.
trycompai/comp
The contract every new or modified API endpoint must follow so it is correct for the public OpenAPI spec, the MCP server (npm @trycompai/mcp-server), the ValidationPipe, and the docs.
WaHaiLong/KingdeeMCP
Knowledge base for the Kingdee MCP Dev Squad. An agent skill from WaHaiLong/KingdeeMCP.
NethServer/nethsecurity
Write or modify a NethSecurity Python RPCD API script or hook.
Jeffallan/claude-skills
Builds async Python APIs with FastAPI and Pydantic V2, covering endpoints, JWT authentication, async SQLAlchemy, WebSockets and pytest checks against the OpenAPI docs.
aiskillstore/marketplace
FastAPI web framework patterns. An agent skill from aiskillstore/marketplace.
latitude-dev/latitude-llm
Configure Better Auth server and client, set up database adapters, manage sessions, add plugins, and handle environment variables.
latitude-dev/latitude-llm
Create, validate, preview, and publish self-contained HTML artifacts.
latitude-dev/latitude-llm
Continuously monitor GitHub PR CI checks and automatically fix failures until all checks pass.
latitude-dev/latitude-llm
This skill should be used when the user asks to "create a Temporal workflow", "write a Temporal activity", "debug stuck workflow", "fix non-determinism error", "Temporal Python", "Temporal…
latitude-dev/latitude-llm
Review the current conversation context and git changes, then persist durable repository knowledge into dev-docs/.md by domain and into AGENTS.md for cross-cutting repo rules.
latitude-dev/latitude-llm
Enables or disables Latitude production maintenance mode by redirecting all publicly exposed production services to the Better Stack status page.
Categories
Adding or changing API operations in @repo/operations. An agent skill from latitude-dev/latitude-llm. API Endpoints is an agent skill from latitude-dev/latitude-llm. Adding or changing API operations in @repo/operations.
API Endpoints fits situations like: tasks that involve REST APIs; tasks that involve OpenAPI specifications; tasks that involve MCP servers.
Run `npx skills add latitude-dev/latitude-llm --skill api-endpoints -a claude-code`. Or copy the skill folder (.agents/skills/api-endpoints in latitude-dev/latitude-llm) into .claude/skills/api-endpoints in your project. Claude Code loads it when a task matches its description.
Run `npx skills add latitude-dev/latitude-llm --skill api-endpoints -a codex`. Or copy the skill folder (.agents/skills/api-endpoints in latitude-dev/latitude-llm) into .agents/skills/api-endpoints in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add latitude-dev/latitude-llm --skill api-endpoints -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/api-endpoints, .gemini/skills/api-endpoints, .github/skills/api-endpoints and .opencode/skills/api-endpoints in your project.
Going by SKILL.md and its folder, API Endpoints needs the command-line tools its instructions call (pnpm, git, uv, npm and cargo). Our summary lists: Python 3; Docker.
SKILL.md names 1 domain. As links in the text: modelcontextprotocol.io. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
API Endpoints is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.5k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with API Endpoints: OpenAPI to MCP Server (mcp-use/mcp-use, 11k stars), API Endpoint Contract (trycompai/comp, 2k stars), Kingdee MCP Dev (WaHaiLong/KingdeeMCP, 103 stars) and Ns API (NethServer/nethsecurity, 191 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
latitude-dev (a GitHub organization) maintains it in latitude-dev/latitude-llm, which has 4,712 GitHub stars. The repository holds 28 skills in this directory. The repository was last updated on October 6, 2026.
Source: latitude-dev/latitude-llm on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.