| [ Web Proxy ] |
| Viewing: https://async.github.io/api-contract/ | [Back] [Original] |
Beta / Async
Semantic API contract surfaces, ledgers, and impact checks for Async packages.
Track exported features and consumer usage so breaking API changes are caught before release.
pnpm add -D @async/api-contract
pnpm run pipeline:verify
@async/api-contract is the metadata contract layer for packages and local
tools. It lets maintainers describe multiple API concerns from one source, then
emit stable artifacts for compatibility review, programmatic invocation, CLIs,
local dashboards, MCP tools, evidence, and generated clients.
The package still treats compatibility as feature-based:
required.features subset_of supported.features
Package versions, release tags, docs text, README prose, prompt copy, and
dashboard layout hints do not decide compatibility unless a package maps them
to explicit contract-bearing feature ids.
Local developer tools often grow the same behavior in several places:
When those surfaces are maintained separately, they drift.
@async/api-contract keeps behavior behind programmatic handlers and makes CLI,
dashboard, schema, and docs surfaces projections of one metadata source.
MCP tools follow the same rule: they are adapters over operations, not a second
place to implement behavior.
Describe the API once:
Then generate review and adapter artifacts:
api-contract.json;API_SURFACE.md;Generators emit descriptors, routers, manifests, and docs. They do not execute
business logic.
Use feature catalogs and surfaces when the goal is to publish what a package
supports, requires, emits, or consumes.
Use it when:
It emits:
API_SURFACE.md;It deliberately does not own:
Use schema metadata when the goal is to describe input/output shapes for
validation, prompts, forms, tables, or metadata inspection.
Use it when:
It emits:
It deliberately does not own:
Use operation contracts when the goal is to expose callable API behavior.
Use it when:
It emits:
It deliberately does not own:
Use CLI projections when the goal is to generate human commands and machine JSON
endpoints from operation or schema metadata.
Use it when:
It emits:
It deliberately does not own:
Use dashboard projections when the goal is to generate a local inspection UI
from the same metadata.
Use it when:
It emits:
It deliberately does not own:
Use MCP projections when the goal is to expose selected operations as MCP tools.
Use it when:
registerTool() boilerplate without adding an MCPruntime dependency to this package.
It emits:
It deliberately does not own:
Use effects and receipts when the goal is to make local work auditable.
Use it when:
It emits:
It deliberately does not own:
A Surface is normalized runtime data with sorted unique feature ids and a
stable hash. Compatibility is checked by comparing required features with
supported features.
A schema is a portable description of shape plus optional generation hints.
Schemas can provide JSON Schema, defaults, examples, enum labels, field help
text, prompt hints, and widget hints.
An operation is a callable unit such as project.init, project.verify, or
project.report. Operations can derive compatibility features such as
operation.project.init, so removed callable capabilities appear in normal diff
and impact workflows.
Projections describe user-facing surfaces. A CLI projection names command paths,
flags, positional arguments, prompts, output modes, and machine JSON mapping. A
dashboard projection names form, table, detail, summary, or log views. An MCP
projection names which operations become MCP tools and how those tools should be
described.
Handlers are implementation functions bound to operation ids. A handler is the
programmatic source of behavior. Tests, generated clients, CLI commands,
dashboard transports, and generated MCP tools all invoke handlers through the
same operation id.
Effects document what an operation may do: read files, write files, spawn
processes, make network requests, run agent-style workers, verify browser
output, write to Git, or install packages.
Receipts describe durable evidence written by operations, such as reports,
transcripts, verification logs, or JSON receipts.
Docs references attach bounded source material to operations: README sections,
spec paths, URLs, short summaries, and optional hashes. Full markdown embedding
should be opt-in and size-limited.
Feature ids decide compatibility hashes. Labels, README summaries, CLI prompt
text, and dashboard layout hints stay outside compatibility unless the package
deliberately publishes feature ids for them.
pnpm add @async/api-contract
import { defineApiContract, defineOperation } from "@async/api-contract/interface";
import { jsonSchemaAdapter } from "@async/api-contract/schema";
import {
generateCliDescriptor,
generateDashboardManifest,
generateMcpDescriptor
} from "@async/api-contract/generators";
const projectInitInput = jsonSchemaAdapter<{ name: string }>({
type: "object",
required: ["name"],
properties: {
name: { type: "string" }
}
}, {
parse(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error("input must be an object");
}
const input = value as { name?: unknown };
if (typeof input.name !== "string") throw new Error("name must be a string");
return { name: input.name };
}
});
const projectInitOutput = jsonSchemaAdapter<{ ok: boolean }>({
type: "object",
required: ["ok"],
properties: {
ok: { type: "boolean" }
}
});
const contract = defineApiContract({
packageName: "workspace-tool",
operations: [
defineOperation({
id: "project.init",
title: "Initialize project",
input: projectInitInput,
output: projectInitOutput,
effects: ["filesystem.write"],
cli: {
command: "init",
interactive: true
},
dashboard: {
group: "Project",
view: "form",
resultView: "summary",
transport: "machine-cli"
},
mcp: {
toolName: "project.init",
title: "Initialize project",
resultContent: "json-text"
}
})
]
});
console.log(generateCliDescriptor(contract, { binaryName: "workspace-tool" }));
console.log(generateDashboardManifest(contract, { binaryName: "workspace-tool" }));
console.log(generateMcpDescriptor(contract, { serverName: "workspace-tool" }));
@async/api-contract can generate user-facing surfaces from either:
A CLI can be generated from operation metadata:
const contract = defineApiContract({
packageName: "workspace-tool",
operations: [
defineOperation({
id: "project.init",
title: "Initialize project",
input: projectInitInput,
output: projectInitOutput,
effects: ["filesystem.write"],
cli: {
command: "init",
interactive: true
}
})
]
});
Generated human CLI:
workspace-tool init
Generated machine CLI:
workspace-tool api manifest --json
workspace-tool api describe project.init --json
workspace-tool api invoke project.init --input-json '{"name":"Acme"}'
Rule: dashboards and automation call the machine JSON API, not interactive
prompt output.
A CLI can also be generated from schema metadata alone for tools that only need
validation, inspection, or form-like commands.
A dashboard can be generated from the same metadata:
dashboard: {
group: "Project",
view: "form",
resultView: "summary",
transport: "machine-cli"
}
The generated dashboard manifest describes:
Dashboard rule: the dashboard is a projection. It must not become the durable
state authority.
MCP can be generated from the same operation metadata. In v1, MCP support is
limited to tools over stdio.
import { generateMcpDescriptor, generateMcpServerModule } from "@async/api-contract/generators";
const contract = defineApiContract({
packageName: "workspace-tool",
operations: [
defineOperation({
id: "project.init",
title: "Initialize project",
input: projectInitInput,
output: projectInitOutput,
effects: ["filesystem.write"],
receipts: [{ kind: "report", pathTemplate: "reports/init-{timestamp}.md" }],
mcp: {
toolName: "project.init",
title: "Initialize project",
description: "Create a local project workspace.",
annotations: { destructiveHint: true },
resultContent: "json-text",
featureId: "mcp.project.init"
}
})
]
});
const descriptor = generateMcpDescriptor(contract, {
serverName: "workspace-tool",
serverVersion: "1.0.0"
});
const serverSource = generateMcpServerModule(contract, {
serverName: "workspace-tool",
serverVersion: "1.0.0"
});
The descriptor has format: "api-contract.mcp.v1" and includes package name,
server name, protocol target, tools, schemas, effects, receipts, and feature
ids. It is review data; it does not import or execute the MCP SDK.
The generated server module is TypeScript source text. It imports
invokeOperation() from @async/api-contract, imports McpServer and
StdioServerTransport from the consuming project's MCP SDK install, registers
tools, exports createMcpServer(api) and main(api), and calls
invokeOperation(api, operationId, args).
MCP exposure is explicit by default:
generateMcpDescriptor(contract); // only operations with mcp metadata
generateMcpDescriptor(contract, { exposure: "all" }); // every operation except disabled MCP projections
Use explicit exposure for local-first tools because MCP tools are model-visible
actions. Operation ids can become tool names only when they already match MCP
tool-name guidance. If an operation id contains spaces or other unsafe
characters, set mcp.toolName.
Consumer packages install the official MCP TypeScript SDK only when they need
to run the generated server module. @async/api-contract does not depend on an
MCP runtime package.
Schema-only dashboards or CLIs are valid.
Examples:
This is useful for later host packages, but the core concept stays generic.
Operation-first tools define handlers and invoke them:
import { bindApiHandlers } from "@async/api-contract/interface";
const api = bindApiHandlers(contract, {
"project.init": async (input, context) => {
return createProject(input, context);
}
});
Generated CLI and dashboard surfaces both route through the same operation
handler.
Generated MCP tools route through that same handler path. They should not embed
handler implementation logic in generated source.
defineFeatureCatalog()Declares compatibility features for an API concern.
Use it when you want a reviewed feature inventory with release and lifecycle
metadata.
It emits a normalized feature catalog. It does not execute or validate runtime
behavior.
createSurface()Creates a normalized feature surface with a stable hash.
Use it when you need supported, required, emitted, or consumed feature lists.
It emits sorted feature ids and a hash. It does not decide release policy.
defineSchema()Defines generic schema metadata.
Use it when a shape should be reusable across validation, prompts, forms, and
compatibility features.
It emits schema metadata. It does not enforce host-specific domain rules.
jsonSchemaAdapter()Wraps JSON Schema with optional parsing, defaults, examples, and descriptions.
Use it when generators need portable schema data and invocation still needs
runtime parsing.
It emits a schema adapter. It does not require a validation library.
standardSchemaAdapter()Wraps a Standard Schema-style validator.
Use it when a package already exposes a Standard Schema-compatible object.
It emits a schema adapter. The current helper only supports synchronous parsing.
createSchemaFeature()Maps schema metadata to a compatibility feature.
Use it when changing or removing a schema should be visible in diff and impact
workflows.
It emits a FeatureSpec. It does not automatically decide whether a schema is
contract-bearing.
defineApiContract()Defines the maintained operation source for a package.
Use it when operations, schemas, docs, and projections should serialize into
one interface manifest.
It emits an api-contract.interface.v1 structure. It does not bind handlers.
defineOperation()Normalizes one callable operation with schemas, lifecycle, effects, errors,
receipts, CLI metadata, and dashboard metadata.
Use it when a callable capability should be programmatically invoked or mapped
to generated surfaces.
It emits operation metadata. It does not contain business logic.
bindApiHandlers()Connects operation metadata to implementation functions.
Use it when tests, CLIs, dashboards, and generated clients should share one
handler path.
It emits a bound API. It does not generate a CLI by itself.
invokeOperation()Invokes one bound operation by id, parses input, runs the handler, and parses
output.
Use it as the common runtime path for generated adapters.
It emits the operation result. It does not own retries, permissions, or
transport policy.
createOperationSurface()Converts operations into a compatibility surface.
Use it when removed or renamed operations should appear in existing feature
diffs and impact reports.
It emits a Surface. It does not decide whether schema or projection changes
are breaking.
defineCliProjection()Defines command metadata for a generated CLI.
Use it when operation or schema metadata should become commands, flags, prompts,
machine JSON endpoints, output modes, or exit-code mappings.
It emits CLI projection metadata. It does not parse process arguments.
defineDashboardProjection()Defines local dashboard metadata.
Use it when operation or schema metadata should become forms, tables, details,
summaries, empty states, or local transport calls.
It emits dashboard projection metadata. It does not store durable dashboard
state.
defineMcpProjection()Defines MCP tool projection metadata.
Use it when selected operations should become MCP tools with explicit names,
annotations, result content behavior, or feature ids.
It emits MCP projection metadata. It does not import the MCP SDK or register
runtime tools.
defineProjectionSet()Groups CLI, dashboard, and MCP projections.
Use it when a package wants to serialize projections independently from the
operations that reference them.
It emits a projection set. It does not generate files.
generatePackageManifest()Emits api-contract.json with existing catalogs and surfaces plus
x-interface metadata.
Use it when one source should produce the package manifest.
It emits a manifest. It does not run validation outside the metadata shape.
generateApiSurfaceMarkdown()Renders API_SURFACE.md from a manifest.
Use it when maintainers need deterministic human review output.
It emits markdown. It does not infer hidden runtime behavior.
generateCliDescriptor()Produces a CLI descriptor with human commands and machine-stable JSON commands.
Use it when a CLI should be generated from operation and schema metadata.
It emits a descriptor. It does not execute business logic.
generateDashboardManifest()Produces local dashboard metadata.
Use it when a dashboard should render operations, forms, result views, and local
invoke commands from the same metadata.
It emits a dashboard manifest. It does not become state authority.
generateMcpDescriptor()Produces an MCP descriptor from operation metadata.
Use it when maintainers need reviewable MCP tool exposure before generating or
running a server adapter.
It emits api-contract.mcp.v1 metadata. It does not import or execute MCP SDK
code.
generateMcpServerModule()Produces TypeScript source for a thin MCP stdio server adapter.
Use it when a consuming package wants generated registerTool() boilerplate
that invokes bound operation handlers.
It emits source text with createMcpServer(api) and main(api). It does not
bundle handlers, install dependencies, or add MCP resources and prompts.
generateTypeScriptClient()Produces deterministic TypeScript client source for operation invocation.
Use it when consumers need generated client code instead of hand-written
wrappers.
It emits source text. It does not execute the client.
generateMachineCliRouter()Produces a machine CLI router descriptor.
Use it when dashboards and automation need stable api describe and
api invoke routes.
It emits route metadata. It does not scrape human CLI output.
All examples are neutral and intentionally package-agnostic.
Executable examples live in
[examples/combinations](examples/combinations/README.md).
| Example | Combination | Why use it |
|---|---|---|
[compatibility-only](examples/combinations/compatibility-only/README.md) | Feature catalogs plus supported/required surfaces | Publish capability compatibility and ledgers before adding operation handlers. |
[schema-metadata-only](examples/combinations/schema-metadata-only/README.md) | Generic schema metadata only | Share validation, prompt, form, and table metadata without a runtime API. |
[programmatic-api-only](examples/combinations/programmatic-api-only/README.md) | Operations plus handlers | Establish one invocation path for tests and library consumers. |
[api-plus-cli](examples/combinations/api-plus-cli/README.md) | Operations plus CLI projection | Generate human commands and machine JSON routes while keeping CLI code thin. |
[api-plus-dashboard](examples/combinations/api-plus-dashboard/README.md) | Operations plus dashboard projection | Generate local dashboard view metadata that invokes the same operations. |
[api-plus-mcp](examples/combinations/api-plus-mcp/README.md) | Operations plus MCP projection | Expose selected operations as MCP tools without adding business logic to the MCP layer. |
[schema-plus-cli](examples/combinations/schema-plus-cli/README.md) | Schemas plus CLI projection | Generate validation or inspection command metadata from schemas alone. |
[schema-plus-dashboard](examples/combinations/schema-plus-dashboard/README.md) | Schemas plus dashboard projection | Generate dashboard form/table metadata from schemas before operations exist. |
[full-stack](examples/combinations/full-stack/README.md) | Schemas, operations, handlers, CLI, dashboard, MCP, receipts, manifest, ledger, and generated source | Show the complete one-source contract workflow across all generated surfaces. |
Use names such as workspace-tool, project-console, and acme-tool in
public examples.
api-contract check --manifest api-contract.json
api-contract ledger --manifest api-contract.json --out API_SURFACE.md
api-contract ledger --manifest api-contract.json --check API_SURFACE.md
api-contract diff --before old-api-contract.json --after api-contract.json
api-contract impact --before old-api-contract.json --after api-contract.json --consumers consumers.json
api-contract usage scan --target src --package-name acme-tool --dependency workspace-tool --catalog api-contract.json --out api-usage.json
impact is intended as a cheap preflight for explicit many-repo impact runs.
Read the latest consumer manifests or usage files first; then run full dependent
repo checks only for consumers that actually use changed features.
usage scan is a line-oriented source preflight. It records dependency and
feature-string evidence, but it is not a full parser or proof of semantic usage.
unless they are explicitly mapped to feature ids.
default.
api-contract.package.v1 manifest envelope.x-interface.into schema, operation, CLI, dashboard, MCP, and evidence metadata later.
@async/api-contract
@async/api-contract/schema
@async/api-contract/interface
@async/api-contract/projection
@async/api-contract/generators
@async/api-contract/types
@async/api-contract owns the stable compatibility core: feature catalogs,
surfaces, hashes, manifest parsing, diffing, impact reports, usage scan, and API
surface markdown rendering.
@async/api-contract/schema owns generic schema metadata.
@async/api-contract/interface owns programmatic operation contracts.
@async/api-contract/projection owns CLI, dashboard, and MCP projection
metadata.
@async/api-contract/generators owns deterministic artifact generators.
@async/api-contract/types owns type-only compatibility helpers.
The repository's package scripts and GitHub Actions are generated from
pipeline.ts through @async/pipeline.
pnpm run pipeline:verify
pnpm run pipeline:api-surface
pnpm run pipeline:api-surface:generate
pnpm run pipeline:github:check
pnpm run pipeline:sync:check
pnpm run release:check
Release and preview lifecycle commands are also synced from pipeline.ts:
pnpm run pipeline:preview
pnpm run pipeline:snapshot
pnpm run pipeline:publish
pnpm run pipeline:release:doctor
@async/claims can wrap surfaces with issuer, evidence, trust, policy, and
signatures. It should not redefine feature ids, hashes, derivation, or
compatibility rules.
pnpm run pipeline:pages. Workflow source: pipeline.ts.| Web Proxy Viewer | New URL | Original Page |