Skip to content

feat: OpenAPI 3.x backend, one CLI any tool backend - #2

Merged
tomsun28 merged 10 commits into
mainfrom
openapi-adapter-design
Sep 6, 2026
Merged

feat: OpenAPI 3.x backend, one CLI any tool backend#2
tomsun28 merged 10 commits into
mainfrom
openapi-adapter-design

Conversation

@tomsun28

@tomsun28 tomsun28 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the design in docs/design-openapi-adapter.md: OpenAPI 3.x JSON specs become first-class citizens next to MCP servers, unified behind a neutral Backend contract rather than an openapi→MCP conversion.

agentcli <server> <tool> --flags
          │
    dispatch (backend-agnostic)
          │        ToolDef { name, description, inputSchema }
   ┌──────┴──────┐
 McpBackend    OpenApiBackend
 daemon/direct snapshot → compile → fetch

OpenAPI support

  • agentcli server add <name> --openapi <spec.json|url> [--base-url ...] [--header "Authorization: Bearer ${ENV}"]
  • Spec compiler: operationId → tool name (mechanical slug fallback, deterministic dedup), path/query/header params + JSON body properties flatten into flags (POST /pets {name,tag}--name x --tag y), top-level allOf merged, local $ref inlined with cycle decay, external refs rejected with a hint
  • Snapshots: spec copied to ~/.agentcli/specs/<name>.json at add time — offline-friendly; --refresh re-pulls from origin (URL or file); corrupt snapshot auto re-pulls
  • HTTP → error mapping: 401/403 → AUTH_REQUIRED, 404 → NOT_FOUND, 429/5xx → EXECUTION_ERROR with httpStatus + the API's own error message + retryAfter; ${ENV} header placeholders expand at call time (unset → AUTH_REQUIRED)
  • Server help groups operations by spec tag; relative servers URLs resolve against the spec origin
  • Zero new dependencies (fetch is built-in; $ref resolver is ~80 lines)

Unification refactor

  • Backend interface + neutral ToolResult {data, text?, isError?}; MCP content shapes convert once inside backend/mcp.ts (extractData/parseMaybeEncoded migrated out of dispatch.ts) — the core never sees a protocol-specific shape
  • McpToolToolDef (adds optional tags, openapiMeta)
  • daemon stays MCP-only by design (stateless HTTP gains nothing from it); OpenAPI meta.via is always direct

Docs & positioning

  • README: "One CLI. Any tool backend." — MCP + OpenAPI side by side, backend comparison table, OpenAPI section
  • SKILL.md: OpenAPI registration + HTTP error mapping notes (still < 6KB, guard test updated)
  • package.json: description broadened, version 0.2.0

Test plan

  • 41 new tests: 19 compile units (naming/required/$ref/cycles/tags/collisions/relative servers) + 21 live-HTTP e2e (URL building, body flattening, auth env expansion, full error-code matrix, snapshot independence, --refresh, cleanup on remove, Swagger/YAML rejection) + 1 skill guard

  • All 48 existing MCP tests pass unchanged (regression: dispatch refactor is behavior-neutral)

  • Smoke-tested against the real petstore3.swagger.io spec (19 operations registered; demo API currently 500s server-side for everyone, error mapping verified correct)

  • pnpm build clean, pnpm test 89/89

Port all src/, test/, and fixtures/ files to TypeScript with strict mode
enabled. Compiled output now lives in dist/ (tsc); bin/agentcli.js and
test paths point at dist; shared types are centralized in src/types.ts.

Behavior changes bundled with the migration:
- server remove now also deletes the stale tools cache file for that server
- tool help output lists the --no-daemon global flag
- client version reported to MCP servers is the real package version
  (was hardcoded "0.0.1")

Build and publish hardening:
- engines: ">=20" -> ">=20.10" (import attributes require Node 20.10+)
- files narrowed to bin, dist/src, dist/package.json, skills, README.md
  with negations for *.d.ts and *.map: package shrinks from 56 to 18 files
  (184 kB -> 67 kB unpacked); tests and fixtures no longer ship
- tsconfig: noEmitOnError true, so type errors cannot produce a bad dist
- saveConfig writes ~/.agentcli/config.json with 0600 permissions and also
  tightens pre-existing files (config may hold bearer tokens)

Verified: build and 48/48 tests pass; installed from the real packed
tarball and exercised stdio/HTTP MCP calls, daemon mode, help discovery,
and error surfaces end to end.

BREAKING CHANGE: Node.js >= 20.10 is now required (was >= 20).
- CI: build + test on push to main/ts and PRs to main, Node 20/22 matrix
  to cover the >=20.10 engines floor (import attributes)
- Release: publish to npm automatically when a GitHub release is
  published (NPM_TOKEN secret, NODE_AUTH_TOKEN via registry-url)
- Lint PR: validate conventional-commit PR titles
The lock file contained pnpm-style node_modules/.pnpm link entries, so
npm ci on CI resolved commander to a broken local path and tsc failed
with 'Cannot find module commander'. Regenerate the lock with npm and
drop the stale pnpm-lock.yaml; the project is npm-only.
- add packageManager: pnpm@10.28.0 (corepack pins the toolchain)
- regenerate the lockfile as pnpm-lock.yaml, drop package-lock.json
- make scripts self-contained (build inlines the clean step) because
  pnpm does not run pre/post hooks by default; prepublishOnly now runs
  clean + tsc + tests directly
- CI and Release workflows install with pnpm (pnpm/action-setup reads
  the version from packageManager; frozen lockfile; pnpm cache)
- publishing stays on npm publish: the pnpm packer does not honor the
  negation patterns in the files field, so a pnpm publish would ship
  44 files including .d.ts and source maps instead of the intended
  18-file tarball
- README: install and from-source instructions use pnpm; engines note
  updated to >= 20.10

Verified locally: pnpm install/build/test pass (48/48); npm publish
--dry-run runs prepublishOnly end to end and packs the 18-file tarball.
Unify MCP and OpenAPI behind a neutral Backend contract:
- ToolDef in / ToolResult out; MCP content shapes converted inside McpBackend
  (extractData/parseMaybeEncoded migrated from dispatch.ts)
- OpenAPI spec compiler: operations -> tools, params + JSON body properties
  flatten into flags, local $ref inlining with cycle decay, tag-grouped help
- spec snapshots at add time (offline-friendly), --refresh re-pulls origin
- HTTP status -> typed errors (401/403 AUTH_REQUIRED, 404 NOT_FOUND, 4xx/5xx
  EXECUTION_ERROR with httpStatus + API message), ${ENV} header expansion
- zero new dependencies; 41 new tests (compile units + live HTTP e2e),
  all 48 existing MCP tests unchanged
@tomsun28 tomsun28 changed the title feat: OpenAPI 3.x backend — one CLI, any tool backend feat: OpenAPI 3.x backend, one CLI any tool backend Sep 6, 2026
@tomsun28
tomsun28 merged commit 04a43a0 into main Sep 6, 2026
3 checks passed
@tomsun28
tomsun28 deleted the openapi-adapter-design branch September 6, 2026 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant