SDK
Every export of the TypeScript reference SDK, from the Hcp facade and runtime drivers to lints, grants, access, check, host adoption and HarnessAgent.
@harnesscontextprotocol/sdk (TypeScript, ESM, Zod schemas) is the reference
implementation of the Specification, and the spec
governs wherever the two differ. Most hosts need only the
Hcp facade to load, activate and run packs, and the sections
after it list the lower-level exports.
bun add @harnesscontextprotocol/sdkThe package is a private preview on GitHub Packages, and
Install the CLI shows the .npmrc it needs.
Hcp facade#
import { Hcp } from '@harnesscontextprotocol/sdk'
const hcp = await Hcp.create({
packDirs: ['./packs/transit', './packs/ledger'],
sandbox: true,
grants: [{ capability: 'verb.exec.read' }],
principal: { id: 'u_1', kind: 'human' },
tenant_id: 't_1',
project_id: null,
})
await hcp.activate(['transit'])
const res = await hcp.exec('transit', 'plan', { from: 'Zurich', to: 'Bern' })Hcp.create(opts): Promise<Hcp>#
| Option | Type | Default | Notes |
|---|---|---|---|
runtime | HcpRuntimeDriver | 'local' | 'proprietary-stub' | 'local' | Pass your own driver to embed |
packDirs | string[] | none | Required for 'local' |
packs | LoadedHcpPack[] | none | Required for 'proprietary-stub' |
sandbox | boolean | true | |
nativeLocal | boolean | false | native-local only when sandbox: false && nativeLocal: true |
grants | HcpGrant[] | permissiveGrants() | |
principal | HcpPrincipal | { id: 'local-principal', kind: 'human' } | |
tenant_id | string | 'local-tenant' | |
project_id | string | null | null | Required for land brain |
allowBins | string[] | ['hcpx', 'hcp'] plus every pack's wrap.bin | Sandbox allow-list |
projectDir | string | none | Working directory for packs with wrap.workspace.mode: project; without it those commands fail with no_project |
access | AccessConfig | { mode: 'auto', approvals: 'agent' } | Mode, per-pack modes, rules, approval authority. See Access |
approvals | ApprovalStore | none | Where approval requests wait for a person |
audit | AuditSink | none | Receives one AuditEvent per decision |
views | ApprovalViewOptions | none | Extra approval-view inference and signal rules, tried before the defaults |
Methods#
| Method | Returns | Behaviour |
|---|---|---|
activate(ids) | Promise<void> | Throws cannot activate unknown pack: <id> |
activatedPacks() | string[] | |
connect(id) | Promise<void> | Throws if not activated or the driver's connect fails |
exec(id, verb, args?, opts?) | Promise<HcpExecResult> | Returns ok: false for unactivated packs; delegates to the driver. opts.approval: { by: 'human', id? } is the host's statement that its own UI collected a person's yes for exactly this call |
listVerbs(id) | Promise<string[]> | |
skill(id) | Promise<string> | HARNESS.md text or '' |
listPacks() | Promise<LoadedHcpPack[]> | |
setGrants(grants) | void | Replaces the authz grants |
sandboxMode | HcpSandboxMode | 'sandbox' | 'native-local' |
authz | HcpAuthzContext | Principal, tenant, project, grants |
Runtime drivers#
type HcpRuntimeDriver = {
id: string
kind: 'reference-local' | 'proprietary-stub' | string
listPacks(): Promise<LoadedHcpPack[]>
getPack(pack_id: string): Promise<LoadedHcpPack | null>
connect?(pack_id: string, authz: HcpAuthzContext): Promise<{ ok: boolean; error?: string }>
exec(args: {
pack_id: string; verb_key: string; args?: Record<string, unknown>
authz: HcpAuthzContext; sandbox: HcpSandboxMode
approval?: HcpHostApproval
}): Promise<HcpExecResult>
}createReferenceLocalRuntime({ packDirs, allowBins?, projectDir?, access?, approvals?, audit?, views? })runs wrap and composite adapters with a process allow-list, a cwd jail, andHCP_SANDBOXset in the child, implementing the pipeline in §6.3.createProprietaryStubRuntime({ packs, grants, principal, access?, approvals?, audit?, views? })runs the same authorization pipeline with a stubbed adapter step, as the starting point for a hosted runtime (Build runtimes).
Both come wrapped in access control. Hcp.create({ runtime: myDriver }) wraps
your own driver the same way.
type HcpExecResult = {
ok: boolean; pack_id: string; verb_key: string
result?: unknown; error?: string; stdout?: string; stderr?: string
code?: string // stable reason when ok is false: unknown_argument, confirm_required, …
hint?: string // what to do next, written for an agent
preview?: { command: string; args: Record<string, unknown>; cli?: string } // on confirm_required
approval?: HcpApprovalRequest // on confirm_required
approval_id?: string
access?: { mode: string; reason: string; approvals: string }
}Loading and linting#
| Export | Purpose |
|---|---|
loadPackFromDir(dir) | Reads the three files, parses hcp meta, returns LoadedHcpPack |
lintLoadedPack(pack) | Runs lintHarnessPack + lintHcpPolicy; returns HarnessPackLintIssue[] |
lintHarnessPack({...}) | Manifest-level lints (presence, meta, wrap, plane, openapi, sense_only) |
lintHcpPolicy({...}) | Coverage lints (missing/extra verbs and stimuli, surface_id match) |
parseHarnessPackMeta(raw) | Zod parse of the hcp block |
parseHcpPolicyFile(raw) | Zod parse of policy.json |
getVerbPolicy(file, key), getStimulusPolicy(file, ref) | Lookups |
applyHarnessPackMeta(meta) | Runtime projection (bin, cwd, env) from meta |
wrapAgentEnvFromMeta(meta, env?) | Merged child environment |
type LoadedHcpPack = {
pack_id: string; dir: string
manifest: HcpPackManifest; policy: HcpPolicyFile
harness_md: string; harness_present: boolean
adapter_kind: HarnessAdapterKind; binding?: HarnessBinding; wrap?: HarnessWrapTarget
verb_keys: string[]
}Schemas#
These Zod schemas validate input and generate JSON Schema:
HarnessPackMetaSchema, HarnessBindingSchema, HarnessAdapterKindSchema,
HarnessWrapTargetSchema, HarnessPassThroughSchema, HarnessOpenApiRefSchema,
HcpPolicyFileSchema, HcpPolicyEntrySchema, HcpPolicyOperationSchema,
HcpPrivacyLandSchema.
The constants are HCP_PROTOCOL_ID = 'hcp' and HCP_VERSION = 1, and
Schemas prints the JSON Schema generated from them.
Grants#
const HCP_CAPABILITIES = ['pack.install', 'pack.connect', 'verb.exec.read', 'verb.exec.write', 'sense.enable'] as const
type HcpGrant = { capability: HcpCapability; pack_id?: string; verb_keys?: string[] }
type HcpPrincipal = { id: string; kind: 'human' | 'service' | 'agent' | 'mothership' }
type HcpAuthzContext = { principal: HcpPrincipal; tenant_id: string; project_id: string | null; grants: HcpGrant[] }
type HcpAuthorize = (ctx: HcpAuthzContext, req: HcpExecRequest) => HcpAuthzDecisiondefaultAuthorize implements §9.4 and §9.5:
land brain requires project_id, the Policy operation picks the capability,
and grant matching narrows by pack_id and then by verb_keys.
permissiveGrants() returns one unscoped grant per capability.
Access and approvals#
These exports implement the access layer of draft RFC 0006 and the approval options of draft RFC 0007, and Access explains the behaviour.
| Export | Purpose |
|---|---|
withAccess(driver, opts) | Puts argument checks, grants (with opts.authorize), the access decision, and audit in front of any driver. Wrapping twice is a no-op |
gateCommand({ pack, verb_key, args, access?, approvals?, approval?, views? }) | The decision for one call: allow (with who satisfied it), ask (with the HcpApprovalRequest and the result to return), or deny |
decideAccess(req, config?) | Mode and rules only: allow, ask, or deny, with code and reason |
describeCommand(pack, verb_key, args) | Pack name, summary, arguments with descriptions, and the exact CLI line |
mergeAccess({ user, project, env }), loadEffectiveAccess(files, env?) | Layered settings; project settings only tighten |
accessFiles(home, projectRoot), readAccessFile(path), writeAccessFile(path, config), envAccess(env?) | The access.json files and HCP_MODE / HCP_APPROVALS |
requestApproval, consumeApproval, decideApproval, memoryApprovalStore(), fileApprovalStore(path) | Approval requests: one run, exact arguments, 15 minutes |
fileAuditSink(path), readAudit(path, filter?), argsHash(args) | The audit log and the argument hash it records |
approvalView(pack, verb_key, args, opts?) | The typed card: kind, title, fields, rest, signals, sources, inferred |
suggestPreview(pack, verb_key, argNames), lintApprovalPreview(pack, verb_key, argNames) | The preview an author should declare, and the problems with a declared one |
formatApprovalView(view, cli?) | The card as plain lines, as hcpx preview prints it |
DEFAULT_VIEW_RULES, DEFAULT_SIGNAL_RULES, APPROVAL_VIEW_KINDS, APPROVAL_VIEW_FIELDS | The inference data and the kinds hosts draw |
approvalOptions({ pack_id, verb_key, policyConfirm, view, args }) | allow_once, reject_once, and, where allowed, allow_session with its rule |
argsFromArgv(pack, verb_key, argv) | Arguments from a command line an agent wrote |
verbArguments(pack, verb_key) | A command's arguments: name, required, description, example |
Command titles#
| Export | Purpose |
|---|---|
commandTitle(pack, verb_key, args, result?) | { live?, done? } for this call, or null when the command declares no title. Pass the JSON result to fill {{result.…}} in done |
lintCommandTitle(pack, verb_key, argNames) | The problems hcpx check reports as M026 |
Titles explains how a pack declares them.
Check and eval#
The check entry point exposes hcpx check and hcpx eval as functions:
import { checkPack, formatCheckForAgent, initEvalFile } from '@harnesscontextprotocol/sdk/check'
await initEvalFile('./packs/gh') // { written, file, cases }; never overwrites
const report = await checkPack('./packs/gh', { probe: true })
if (!report.ok) console.log(formatCheckForAgent(report))| Export | Purpose |
|---|---|
checkPack(dir, opts?) | The full report: ok, score, grade, summary, categories, findings, probes, evals, layers, next. Options: probe, eval (default: when the file exists), timeoutMs (default 10000), docsBase, displayDir |
readEvalCases(dir) | The cases of hcp.eval.json, or null when there is no file |
scaffoldEvalCases(pack), initEvalFile(dir) | The starter cases --init-evals writes |
formatCheckReport(report), formatCheckForAgent(report) | The terminal report and the Markdown fix list |
resolveWrapBin(pack) | wrap.bin resolved the way the runtime does: in the pack, else on PATH |
EVAL_FILE | 'hcp.eval.json' |
type EvalCase = {
name: string
verb: string
args?: Record<string, unknown>
expect?: { ok?: boolean; error?: string; contains?: string[]; not_contains?: string[]
json?: boolean; max_ms?: number; max_tokens?: number }
}Check and evaluate lists the rules and how cases are graded.
Availability#
buildPackAvailability({ pack, installed, activated, connected, grants, source_available })
returns HcpPackAvailability with a per-command executable and a reason
(not installed, not activated, missing <capability>). hcpx availability
is built on it.
Generation and adoption#
| Export | Purpose |
|---|---|
generateHarnessSurfaces(hcp, opts?) | Prompt block, tools, skills, per-host configs from activated packs. opts.cliBin (default 'hcp') is the command written into host config |
hcpToolName(pack_id, verb_key) | <pack_id>__<verb_key>, dots as underscores |
adoptOpenCode(surfaces, cwd, { write, pluginEntry }) | Writes opencode.json + plugin wiring |
adoptCursor(surfaces, cwd, { write }) | Writes .cursor/ rules and skills |
adoptAgent, adoptMcp, adoptGrok (surfaces, cwd, { write }) | The generic plugin, the MCP export, and the Grok files |
refuseProductRuntimeAdopt(cwd) | Throws when the workspace has a .hcp/no-adopt file |
buildHcpContextBundle(hcp, opts?) | Stable prefix + leads + hcp__read_skill tool. See Context optimization |
hcpToToolLoopAgent(hcp, opts) | AI SDK tool set from an Hcp |
Host intake#
A host that consumes a generated surface manifest, and does not embed the
runtime, uses loadSurfaceManifest(cwd), loadHostIntake(cwd, opts?),
resolveSurfaceTool(manifest, toolName) and execVerbViaCli(pack_id, verb_key, { args, cli, home, cwd }).
The last runs hcpx exec, or the command in cli, and parses the JSON, failing
with non-json cli output when stdout is not JSON.
HarnessAgent#
import { HarnessAgent } from '@harnesscontextprotocol/sdk'
const agent = await HarnessAgent.create({
runtime: 'ai-sdk', model, packDirs: ['./packs/transit'],
grants: [{ capability: 'verb.exec.read' }],
})
for await (const ev of agent.run('Plan Zurich → Bern at 9')) { … }AI SDK covers the options, the events and the approval flow.
Store helpers#
resolveHcpHome(env?), ensureHcpHome(), ensureBootstrapPack(dir, layout),
installPackFromDir(src, layout), uninstallPack(id, layout),
listInstalledPackDirs(layout), readCliState(layout), writeCliState(state, layout)
and installPackSkill(pack, layout) back the CLI store.
Deprecated aliases#
Systems* policy schema names and parseSystemsSurfacePolicyFile /
lintSystemsSurfacePolicy remain exported and map to their Hcp* equivalents.