Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Tool adapter architecture

Harn treats an integration as an executable typed registry, not as an MCP server, a command-line program, or an OpenAPI document. Those formats are inputs and projections around one runtime-owned contract.

Diagram source
flowchart LR
    O[OpenAPI or another source] --> G[Typed adapter generator]
    H[Handwritten Harn] --> R[ToolRegistry]
    G --> R
    R --> C[harn-tools catalog]
    R --> M[MCP server]
    R --> L[Generated CLI]
    R --> D[Operator dashboard]
    R --> A[Agent runtime]

Why OpenAPI is an input#

OpenAPI 3.1 already supplies most operation data Harn needs: stable operation identifiers, descriptions, parameters, request and response JSON Schemas, security declarations, and HTTP bindings. Its standard extension mechanism also permits namespaced x-harn fields for presentation choices that are not part of HTTP.

OpenAPI does not own executable handler closures, Harn capability authority, approval policy, deferred tool loading, or the lifecycle of non-HTTP integrations. It also does not describe MCP resources and prompts or every CLI presentation choice. Making an OpenAPI-shaped catalog the runtime owner would force handwritten, GraphQL, protobuf, database, and local-library integrations through an HTTP-specific abstraction.

harn-openapi therefore translates operations into typed SDK functions and a ToolRegistry. Its x-harn object only overrides metadata that OpenAPI does not standardize. Future generators should end at the same registry boundary.

The owning contract#

A live ToolRegistry contains the portable contract and the executable handler for each operation. Harn validates and normalizes it once when an adapter publishes or projects the registry. The normalized entry contains:

  • stable name, title, and description;
  • input, output, and declared application-error JSON Schemas;
  • one declared execution backend, with a local handler closure for in-VM tools;
  • Harn execution policy and protocol annotations;
  • closed CLI, MCP, catalog, dashboard, and agent/model audiences;
  • CLI parent metadata plus leaf paths, argument tokens, help, and completion hints;
  • typed source coordinates with a protocol-specific binding object;
  • deferred-loading, icon, execution, and namespaced extension metadata.

The handler is intentionally absent from the serializable harn-tools/2.0 catalog. A catalog can cross a process or language boundary; execution remains attached to the live registry in its owning Harn VM.

Adapter behavior#

The CLI and MCP adapters load a script through the same registry loader. The loader installs project capabilities and connectors, runs the script once, captures every published capability on the same thread, validates the complete registry, and retains the VM and connector lease for later dispatch.

MCP projects only MCP-audience entries onto protocol discovery and calls their original handlers. The CLI builds its command tree only from CLI-audience entries, validates merged JSON and flag input against the same schema, calls that handler, validates its output or declared error, and renders the selected output format. The static catalog applies its own audience through the same normalization without carrying runtime-only values. A future dashboard adapter consumes the same normalized governance field rather than maintaining another exposure list.

The agent loop is also an adapter. It projects the executable registry once at its option boundary, before prompt guidance, progressive search, surface narrowing, schema generation, or dispatch can inspect it. Direct LLM calls use the same normalized agent projection. A tool intentionally reserved for an operator surface therefore cannot leak its name or instructions to the model, appear in a search result, or execute through a forged call.

MCP needs each tool schema to stand alone. Its projection follows reachable catalog components, moves them under $defs, and rebases their local references. This preserves schema meaning for ordinary component graphs. Harn fails server preparation when resource-scope keywords or external references make that move unsafe. The portable catalog remains able to represent the full Draft 2020-12 vocabulary; a transport projection must not narrow its owner.

MCP 2026-07-28 permits any JSON value in structured tool output. Harn therefore preserves the catalog's canonical output schema and value without an adapter-specific wrapper. Inline calls and task completions share one CallToolResult projection, so asynchronous execution cannot create a second result contract.

At load time Harn turns the portable catalog into one immutable prepared catalog. It compiles Draft 2020-12 input, output, and application-error validators, stable name lookup, MCP discovery documents, and a framework-independent CLI tree once. CLI parsing, help, shell completion, MCP, tasks, replay, and exported function dispatch all use that object. They do not rebuild command trees, compile schemas, or recover component context per request.

A raw Harn throw becomes application data only when the tool has an errorSchema and the value satisfies it. Undeclared throws and VM control failures remain runtime errors. A declared throw with the wrong shape is a contract violation. Human diagnostics never stringify an undeclared thrown value; they report only its value-free runtime category. CLI and MCP consume this one classification: JSON CLI output uses a closed failure envelope, while MCP returns isError: true with typed data below _meta["com.harnlang/toolContract"].applicationError. Neither adapter publishes error data as success structuredContent.

This contract sits below both transport formats. OpenAPI Response Objects describe payloads attached to HTTP status codes, so an HTTP adapter can project Harn application errors into a 422 response without making HTTP the semantic owner. MCP tools declare inputSchema and outputSchema, but the tool contract has no declared error schema. Harn therefore uses MCP's _meta extension point for discovery and keeps isError as the protocol-native result signal.

Language bridges#

harn-tools/2.0 is the stable boundary for documentation, compatibility checks, and generated types in other languages. It is not a remote execution protocol by itself. A language bridge needs two pieces:

Version 2 is a direct pre-launch cutover. It adds the application-error contract and intentionally rejects version 1 catalogs. Regenerate the catalog, JSON Schema, and language bindings together rather than mixing readers and writers from the two versions.

  1. generated native input and output types from the catalog's JSON Schemas;
  2. an explicit transport or embedding boundary that resolves a catalog entry to its live handler.

Generated native APIs can map outputSchema to their success type and errorSchema to their application-error type without confusing a Harn Result<T, E> return value with a thrown E. Result<T, E> remains ordinary success-channel data unless the callable separately declares throws E.

That separation keeps type generation reusable across in-process embedding, MCP, HTTP, and future transports. A bridge must not infer execution semantics from _meta; Harn policy and source coordinates have typed fields.

Harn publishes the catalog contract as Draft 2020-12 JSON Schema and strict TypeScript. Both artifacts come from the Rust DTOs that validate live registry and export catalogs. This makes the DTO module the semantic owner and the published files mechanical projections. OpenAPI extensions, MCP fields, and SDK-local interfaces cannot silently become competing catalog definitions.

Static Harn exports project into the same catalog without executing main. This route resolves Harn input and output types into JSON Schemas for SDK generation. It does not import route configuration, authentication, queues, retries, or worker policy into the portable contract.

Current boundary#

The registry now covers executable tools, a nested CLI with portable argument metadata and shell completion, MCP tool discovery and dispatch, static export inspection, and a portable generated contract. MCP resources and prompts share the script loader. They are not part of the tool catalog because they have different invocation and content contracts. Streaming output, interactive CLI prompts, pagination UX, installable completion packaging, and generated native language packages are later projections. They should extend this registry or a sibling typed capability registry, not create another operation owner.