@mcp-com-ai/plugin-capability-graph
v1.3.0
Published
Compile OpenAPI documents into deterministic HAPI capability graphs.
Maintainers
Readme
@mcp-com-ai/plugin-capability-graph
Public OSS compiler and CLI plugin for HAPI capability graphs.
The plugin exposes the OpenAPI compiler as a library, deterministic MCP tool discovery, and offline graph commands. Runtime graph mode is opt-in and leaves existing eager and deferred-search behavior unchanged when disabled.
The package has no Enterprise classifier or hosted AI dependencies.
Compiler API
import {
compileCapabilityGraph,
serializeCapabilityGraph,
} from "@mcp-com-ai/plugin-capability-graph";
const graph = await compileCapabilityGraph(openApiDocument, {
source: {
identifier: "crm-api",
canonicalUri: "https://api.example.com/openapi.json",
},
});
const canonicalJson = serializeCapabilityGraph(graph);compileCapabilityGraph accepts one OpenAPI 3.x object and returns a deeply
frozen CapabilityGraphV1. Invalid supported x-hapi-* extensions reject with
CapabilityGraphCompilationError, whose diagnostics identify the operation,
JSON pointer, and validation error.
Supported optional operation extensions:
x-hapi-capabilityx-hapi-intentx-hapi-effectsx-hapi-prerequisitesx-hapi-planning
A response object may also declare x-hapi-outcome, described under
Response outcomes. Webhook and callback operations may
declare x-hapi-event (see Event triggers), and the
document root may declare x-hapi-client-facts (see
Client facts).
Example:
paths:
/orders/{orderId}/cancel:
post:
operationId: cancelOrder
x-hapi-capability: [orders/management, customer-support/orders]
x-hapi-intent:
use-when: [The customer asks to cancel an open order]
avoid-when: [The order has already shipped]
x-hapi-effects:
mode: write
side-effecting: true
destructive: true
requires-confirmation: true
x-hapi-prerequisites: [The order exists and is still cancellable]Missing semantics are inferred deterministically and retain provenance. Schema
summaries preserve $ref values instead of expanding them, so recursive models
remain bounded. Equivalent path, tag, object-property, and oneOf ordering
produces the same operation identities and graph fingerprint.
Runtime discovery
Enable deterministic graph-backed tool discovery for a native server:
hapi serve --specs ./openapi.yaml --mcp --capability-graphThe flag implies --deferred-tool-search. Cloudflare deployments expose the
same behavior through hapi cloudflare deploy --capability-graph, which sets
HAPI_CAPABILITY_GRAPH=true. The graph is compiled once during runtime
initialization, and malformed explicit x-hapi-* metadata fails startup.
Offline commands
hapi graph compile ./openapi.yaml > graph.json
hapi graph compile --specs https://api.example.com/openapi.yaml --out graph.json
hapi graph inspect graph.json
hapi graph inspect ./openapi.yaml --format graphml > graph.graphml
hapi graph visualize graph.json --out capability-graph.html
hapi graph visualize ./openapi.yaml > capability-graph.html
hapi graph visualize graph.json --out plan.html \
--initial-fact patient.identity.verified \
--initial-fact slot.selected \
--goal-fact appointment.booked
hapi graph search graph.json "close the deal" --limit 5
hapi graph eval graph.json ./evaluation.jsonAll source-taking graph commands accept either a positional source or
--specs <path-or-http(s)-url>. Remote loads use bounded size, timeout, and
redirect handling. Supplying both selectors is rejected. --url is not
accepted because these commands compile and inspect documents without calling
an upstream API. Direct Arazzo documents remain owned by hapi arazzo and
hapi serve --specs; the graph compiler input is one OpenAPI document or a
canonical graph.
compile emits canonical, round-trippable JSON by default. inspect provides
a concise human summary or deterministic GraphML. visualize accepts canonical
JSON or compiles one OpenAPI document and emits byte-stable, self-contained
HTML. search uses the runtime deterministic classifier. eval exits non-zero
when any fixture expectation fails.
Canonical JSON is the lossless visualization input; GraphML is export-only and
cannot be read back by visualize. The HTML viewer is single-graph, local, and
read-only. It has no remote assets, network requests, cookies, browser storage,
service worker, API invocation, or execution control. The topology and local
text/filter results are inspection aids and do not imply authorization or
execution eligibility. Planning transitions, when present, appear in a
separate view. The Business State Flow deduplicates canonical predicate/value
facts and connects every prerequisite to its operation or workflow before
showing produced and removed facts. Facts are conditions; the planner's state
is the complete set of facts currently true, so this view is intentionally not
presented as a formal UML state machine. Compiler-generated
operation.*.completed facts are hidden by default and remain available with
the technical-fact toggle.
Supplying at least one --goal-fact embeds an exact Plan Trace produced by the
same deterministic planner and inputs as graph simulate. The trace shows the
initial state and every before/after fact set for planned, complete,
clarification-required, and unplannable outcomes. An unplannable result still
produces a successful HTML artifact; the outcome is displayed in the file.
Planning-only options without a goal are rejected. No planner or principal
data is embedded in the browser.
The same trace also drives a Flow cards view. It shows a start card with
the goal and known facts, one card per planner step in planner order, and an
outcome card. A step with an unsatisfied gate says "Wait for confirmation"
(or the gate kind) in text. Steps that do not depend on each other through
declared facts share a row. That is not a promise of parallel execution, and
every card keeps its planner step number. Open the artifact on this view with
--initial-view flow-cards:
hapi graph visualize graph.json --out trip-flow.html \
--initial-fact traveller.identity.verified \
--goal-fact trip.booked \
--initial-view flow-cardsEach operation card lists its required inputs: required parameters and required body property names, never credentials. It says "Needs input" when no earlier step in the plan has a planning-eligible OpenAPI Link to that operation. Otherwise it names the link and step that may supply them. Compiled graphs do not keep Link parameter mappings, so the card never claims which input a link fills. These markers are advisory and do not change the plan, its gates, or its outcome.
Fact audit
hapi graph audit checks that every planning fact can be established and
every outcome can be reached:
hapi graph audit graph.json --client-verified-fact traveller.identity.verified
hapi graph audit ./openapi.yaml --no-reachability --output jsonIt reports:
- Orphan requirements. A business fact some transition requires, that no
transition produces, and that is not declared with
--client-verified-fact. - Client-verified facts. Facts established outside the API, such as
verified identity, declared in the contract with
x-hapi-client-factsor passed with--client-verified-fact. Each shows its origin. - Outcomes. Business facts produced and never required. These are the candidate goals.
- Response and event facts. Facts produced by a declared response outcome or signalled by a declared webhook or callback. They are not orphans, and they are never planned.
- Discovery-only operations. Operations that no planning transition targets.
- Reachability. Each outcome, or each
--goal-fact, is planned from the client-verified facts with every operation eligible and every non-authorization gate treated as satisfied. This is a topology check. It does not grant authorization or confirmation.
Compiler-generated operation.*.completed facts are excluded. The command
exits 0 when clean, 1 when an orphan or unreachable outcome is found, and 2
for usage, input, or compilation errors, so it can gate CI. graph visualize
accepts the same --client-verified-fact option and shows the static findings
as gap cards in the Flow cards view, without running the reachability sweep.
Generated HTML contains allowlisted graph summaries, authorization scheme
names/scopes, diagnostics, provenance, and planning facts. Handle it with the
same sensitivity as its source graph. The renderer limits input to 16 MiB,
5,000 nodes, 20,000 edges, 10,000 planning transitions, 10,000 unique planning
facts, 50,000 state relations, 1,000 trace steps, 2,000 diagnostics, and 32 MiB
of generated HTML. Unsupported versions, malformed references, or exceeded
limits fail before stdout or the atomically written --out file is committed.
The pure browser-safe API is available separately:
import {
projectCapabilityGraphVisualization,
renderCapabilityGraphVisualizationHtml,
} from "@mcp-com-ai/plugin-capability-graph/visualization";
const document = projectCapabilityGraphVisualization(graph);
const html = await renderCapabilityGraphVisualizationHtml(document);OSS and Enterprise boundary
This package owns graph compilation, inspection, exports, and deterministic classification. It never loads a licensed classifier or AI SDK.
Probabilistic transition authoring is intentionally separate. A future
licensed capability-graph-transition-assistant may use Jev to score candidate
relationships, but suggestions cannot affect this viewer, the graph
fingerprint, or planning until a user accepts them into x-hapi-planning or
Arazzo and recompiles the contract.
The TypeSafe/Jev classifier is implemented in the separate private
@mcp-com-ai/enterprise-plugin-capability-classifier package. It registers an
alternate classifier through Core's versioned provider seam after checking the
core.enterprise or capability.graph.ai entitlement. Native managed
extensions and a private static Worker profile provide the licensed code; the
public npm package, standalone binary, Docker image, and Worker remain
deterministic-only.
Operators with the private extension explicitly opt in with both graph mode
and --capability-classifier typesafe. Omission remains deterministic.
AOT planning contracts
The package compiles and plans over the closed x-hapi-planning v1 shape:
x-hapi-planning:
requires:
- predicate: order.open
produces:
- predicate: order.approved
removes:
- predicate: order.open
cost: 1produces is required and non-empty. Predicates are lowercase canonical names,
fact values must be JSON-safe, and cost must be finite and nonnegative. This
metadata, fully mapped OpenAPI Links, and supported validated Arazzo workflows
are the only transition sources. Missing planning metadata is never invented.
Simulate offline:
hapi graph simulate ./openapi.yaml \
--initial-fact order.open \
--goal-fact order.approved \
--satisfied-gate mutation-confirmation \
--output jsonThe planner uses finite nonnegative costs and bounded Dijkstra search. Defaults
are depth 12, 2,000 states, 1,000 frontier entries, and 100 expansions per
state; every bound has a CLI override. Outcomes are planned,
clarification-required, complete, or unplannable. Authorization removes
ineligible operations before search. Policy, risk, and confirmation gates are
reported explicitly and never hidden in path cost.
Response outcomes
A non-success response can declare the facts it produces or removes:
responses:
"409":
description: Hotel is unavailable
x-hapi-outcome:
produces: [{ predicate: hotel.unavailable }]
removes: [{ predicate: hotel.selected }]produces and removes use the x-hapi-planning fact rules, and at least
one must be non-empty. The status key may be a code, an NXX range, or
default, but never a 2xx status, because x-hapi-planning already models
success. The operation must have x-hapi-planning; otherwise the outcome is
ignored with an outcome-without-planning warning.
Outcomes are alternatives the client cannot choose, so the planner never plans
through one. They appear on each plan step as outcomes, including in the
bounded capability_plan result. A failed guarded execution step reports the
matching outcome as declaredOutcome, matching the exact status, then the
range, then default. The facts are reported and never applied to execution
state: the client verifies the response and re-plans. Flow cards show outcomes
as "Otherwise" lines, and hapi graph audit lists their facts as response
facts rather than orphans. Documents without outcomes keep their fingerprint.
Event triggers
The compiler records OpenAPI 3.1 webhooks and operation callbacks as
descriptive triggers on the graph. A trigger's id is
webhook:<name>:<method> or callback:<operationId>:<name>:<method>. A
webhook or callback operation can declare the facts its event signals:
webhooks:
orderPaid:
post:
summary: The payment provider confirmed payment
x-hapi-event:
produces: [{ predicate: order.paid }]produces must be a non-empty canonical fact array. x-hapi-event on an
ordinary paths operation is ignored with an event-on-path-operation
warning, and an unresolvable webhook or callback reference is skipped with an
unresolved-trigger warning.
Triggers are descriptive only. HAPI never subscribes to, receives, or plans
through an event. A client that receives and verifies the event passes the
fact as verified initial state. Flow cards name a trigger on the start card
when it signals one of the plan's initial facts, and hapi graph audit lists
event-signalled facts as event facts instead of orphans. Documents without
webhooks or callbacks keep their fingerprint. Arazzo AsyncAPI steps are not
supported yet.
Client facts
A document can declare the facts a client establishes outside the API:
x-hapi-client-facts:
- predicate: traveller.identity.verified
description: The client verified the traveller's identity.Entries use the planning fact rules plus an optional non-empty description.
A declared fact that no transition requires compiles with an
unused-client-fact warning. The graph keeps them as clientFacts.
hapi graph audit and graph visualize treat declared facts like
--client-verified-fact values and report each fact's origin as contract,
option, or both. capability_context marks matching requirement cards with
clientEstablished: true so an MCP client knows it must verify them. The
declaration never makes a fact true: capability_plan still accepts only facts
the client verified.
Expose MCP planning only with the second explicit opt-in:
hapi serve --specs ./openapi.yaml --mcp \
--capability-graph --capability-planningFor Workers, deploy with both --capability-graph --capability-planning, or set
HAPI_CAPABILITY_GRAPH=true and HAPI_CAPABILITY_PLANNING=true. The
capability_plan tool accepts bounded initial/goal facts, satisfied gate codes,
and bounds. It returns an allowlisted result with transition identities, costs,
gates, provenance, digests, and diagnostics.
The same explicit planning opt-in also exposes capability_context. Call it
with a natural-language request before planning to receive bounded,
authorization-filtered candidate tools, declared goal facts, fact
relationships, gates, and transition provenance. Its facts are guidance, not
asserted state: select a goal, supply only verified facts to
capability_plan, collect any clarification, then re-plan after a safe tool
result or human choice. Context neither calls an API nor stores state, asks a
user, executes a plan, or grants authority. Compiler-owned
operation.*.completed facts are hidden unless explicitly requested.
Development tracing
For a local demonstration, add --dev --capability-trace to graph-enabled
serve. HAPI emits bounded [HCG_TRACE] records for discovery, context,
planning, graph-backed operation calls, and guarded execution. The records
show declared transition relationships, observed direct calls, and validated
guarded progression distinctly; they never log query text, fact values,
arguments, responses, credentials, or patient data. Trace mode is opt-in and
rejected outside explicit development mode.
Planning never invokes an API, the OpenAPI operation invoker, or the Arazzo executor. It does not return credentials, HTTP requests/responses, raw graph records, or runtime context.
Guarded execution
Guarded execution is public OSS and requires all three explicit options:
hapi serve --specs ./openapi.yaml --mcp \
--capability-graph --capability-planning \
--runtime-authority guardedWorkers use HAPI_CAPABILITY_GRAPH=true, HAPI_CAPABILITY_PLANNING=true, and
HAPI_RUNTIME_AUTHORITY=guarded. The default decision authority continues to
offer discovery and optional planning but never registers capability_execute.
The executor accepts only an exact canonical plan. It recomputes the plan and binds confirmations to the graph fingerprint, plan digest, server principal, and argument-digested mutation set. Confirmation receipts must be issued by a trusted provider registered through Core's versioned verifier seam; this plugin does not expose a confirmation-issuing tool.
Register a trusted verifier during startup before accepting mutation plans:
import { registerCapabilityExecutionConfirmationVerifier } from "@mcp-com-ai/core/capability-execution";
registerCapabilityExecutionConfirmationVerifier({
version: "1",
id: "deployment-confirmation-service",
verify: (receipt, binding, signal) =>
confirmationService.verify(receipt, binding, signal),
});Before every attempt the runtime rechecks confirmation, authorization, policy,
current facts, arguments/body variant, and retained schemas. OpenAPI operations
run through the existing invoker; supported Arazzo workflows run as one atomic
outer step under both workflow and guarded limits. Automatic retries apply only
to read-only or explicitly idempotent operations and normalized transient
failures. Results are completed, partial, rejected, failed, or
cancelled; ambiguous upstream timeouts/network failures are recorded as
indeterminate steps and stop the sequence.
Deployment limits may reduce, but never exceed, the compiled ceilings: 50 plan steps, five minutes total, 60 seconds per step, 100 total attempts, three retries per eligible step, 10 MiB per response, 100 result records, and 500 event records. Public defaults are deliberately lower (12 steps, 60 seconds total, 15 seconds per step, 16 attempts, one retry, 1 MiB responses, 50 result records, and 100 events).
MCP summaries and structured events are allowlisted: raw arguments, bodies, headers/cookies, URL credentials/query values, principal claims, confirmation proofs, fact values, and stacks are excluded. The runtime does not compensate, resume, persist state, or guarantee exactly-once execution. Receipts are short-lived and context-bound, but durable single-use replay enforcement belongs to the confirmation provider or a future persistent receipt store.
