---
title: "SDK"
description: "Every export of the TypeScript reference SDK, from the `Hcp` facade and runtime drivers to lints, grants, access, check, host adoption and HarnessAgent."
canonical: https://harnesscontextprotocol.io/docs/reference/sdk
---

# 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](/docs/specification), and the spec
governs wherever the two differ. Most hosts need only the
[`Hcp` facade](#hcp-facade) to load, activate and run packs, and the sections
after it list the lower-level exports.

```sh
bun add @harnesscontextprotocol/sdk
```

The package is a private preview on GitHub Packages, and
[Install the CLI](/docs/quickstart#install-the-cli) shows the `.npmrc` it needs.

## `Hcp` facade

```ts
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](/docs/concepts/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

```ts
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, and
  `HCP_SANDBOX` set in the child, implementing the pipeline in
  [§6.3](/docs/specification/execute#63-pipeline).
- `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](/docs/runtimes)).

Both come wrapped in access control. `Hcp.create({ runtime: myDriver })` wraps
your own driver the same way.

```ts
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 |

```ts
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](/docs/reference/schemas) prints the JSON Schema generated from them.

## Grants

```ts
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) => HcpAuthzDecision
```

`defaultAuthorize` implements [§9.4 and §9.5](/docs/specification/authorization):
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](https://github.com/harnesscontextprotocol/hcp-protocol/blob/master/rfcs/0006-access-modes.md)
and the approval options of draft RFC 0007, and [Access](/docs/concepts/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](/docs/concepts/command#titles) explains how a pack declares them.

## Check and eval

The `check` entry point exposes `hcpx check` and `hcpx eval` as functions:

```ts
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'` |

```ts
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](/docs/packs/check) 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](/docs/hosts#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`

```ts
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](/docs/hosts/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](/docs/reference/cli#store).

## Deprecated aliases

`Systems*` policy schema names and `parseSystemsSurfacePolicyFile` /
`lintSystemsSurfacePolicy` remain exported and map to their `Hcp*` equivalents.

## Sitemap

- [Documentation](https://harnesscontextprotocol.io/docs)
- [Packs](https://harnesscontextprotocol.io/packs)
- [Glossary](https://harnesscontextprotocol.io/docs/reference/glossary)
- [llms.txt](https://harnesscontextprotocol.io/llms.txt)
- [llms-full.txt](https://harnesscontextprotocol.io/llms-full.txt)
- [OpenAPI](https://harnesscontextprotocol.io/openapi.json)
- [MCP](https://harnesscontextprotocol.io/mcp)
