> ## Documentation Index
> Fetch the complete documentation index at: https://nativeharness.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Connectors

> Let the agent reach GitHub or your own API on a user's behalf — without the credential ever entering the sandbox.

An agent that can only run shell commands can build things but cannot reach anything. A
connector is how it reaches an external system **on one of your users' behalf**, with that
user's credential, without the credential ever being inside the sandbox.

<Note>
  The machinery is built and tested — declarations, the compiler, the gateway, the call token,
  per-actor credentials, outbound safety. **No connector ships yet**, so today you declare your
  own. A GitHub and an HTTP connector are the next thing on the roadmap.
</Note>

## How a call travels

```
  inside the sandbox                    your side
  ──────────────────                    ─────────
  nh call github create_issue '{…}'
        │  NH_CALL_TOKEN (this command only)
        ▼
     the shim  ─── POST ──►  ConnectorEndpoint
                                  │ verify token → resolve actor → check scope
                                  ▼
                             gateway  ── attaches the real credential ──►  api.github.com
```

The sandbox holds a **token minted for one command**, scoped to that workspace and its
attached tools. The credential is attached on your side, on the way out. A script can print an
environment variable as easily as use it, so the variable must not be worth printing.

Every call is recorded as its own execution, linked to the command that made it — which is
why an approval policy can hold a command that *called* something, not only one that wrote a
file.

## 1. Declare it

Most connectors are data, not code: an auth scheme, a `verify` request, and a list of tools
describing where each parameter goes. Nothing runs that the schema did not describe.

```ts theme={null}
import { assertValid } from "@nativeharness/connectors";

const github = {
  schema: 1,
  id: "github",
  name: "GitHub",
  description: "Repositories, issues and pull requests.",
  category: "development",
  // Every host any tool may reach. Enforced per request, so a tool cannot be pointed elsewhere.
  hosts: ["api.github.com"],
  auth: {
    type: "bearer",
    fields: [{ key: "token", label: "Token", type: "secret", required: true }],
    headers: { Authorization: "Bearer {token}", Accept: "application/vnd.github+json" },
    // Run when a user saves a credential, so a bad token fails then rather than mid-turn.
    verify: { method: "GET", url: "https://api.github.com/user", refused: [401, 403] },
  },
  tools: [
    {
      name: "create_issue",
      description: "Open an issue on a repository.",
      method: "POST",
      url: "https://api.github.com/repos/{owner}/{repo}/issues",
      input: {
        type: "object",
        properties: { owner: { type: "string" }, repo: { type: "string" }, title: { type: "string" } },
        required: ["owner", "repo", "title"],
      },
    },
  ],
} as const;

assertValid(github);   // throws with a `declaration` code if it is not well-formed
```

`{param}` placeholders are allowed in the **path only**. Everything else is placed by the
`params` block, or by convention: path parameters by name, then the rest to the query for a
`GET` and to the body otherwise.

A tool is **mutating** unless it is a `GET`, and a `discard` session refuses mutating tools —
so a read-only session cannot open issues.

## 2. Attach it to a workspace

Attaching says *this user's workspace may use this connector, with this credential, limited to
these tools*:

```ts theme={null}
await harness.store.updateWorkspace(workspace.id, {
  connectors: [{
    connector: "github",
    // A reference YOU resolve — a row id, a vault key. Never the secret itself, and always
    // `null` in an export.
    credential: "cred_8f21",
    tools: ["create_issue"],      // or "*"
  }],
});
```

## 3. Wire the gateway and the endpoint

Two pieces on your side: a **gateway** that knows every connector and can resolve a credential
reference into a real secret, and an **endpoint** the shim posts to.

```ts theme={null}
import { ConnectorGatewayImpl, ConnectorEndpoint, connectorSupport } from "@nativeharness/connectors";

const gateway = new ConnectorGatewayImpl({
  store: harness.store,
  registry: { github },
  // Your vault. The only place the secret exists.
  credentials: async (reference) => vault.get(reference),
});

const endpoint = new ConnectorEndpoint({ gateway, store: harness.store, key: process.env.NH_TOKEN_SECRET! });
```

Then give `BashTool` the three hooks that put the shim in the guest. `connectorSupport` fills
all three at once, because they only work together — the shim is useless without the token,
the token is useless without the shim on `PATH`:

```ts theme={null}
import { BashTool } from "@nativeharness/core";

const support = connectorSupport({
  // Reachable FROM THE GUEST, which is not always localhost.
  apiUrl: "http://10.0.0.1:3001/connector",
  secret: process.env.NH_TOKEN_SECRET!,
});

const bash = new BashTool({ store, sandbox, events, ...support });
```

A command in a workspace with no active attachment gets no shim and no token at all, rather
than a token that can call nothing.

## 4. The agent uses it

From inside a command, three verbs — and the system prompt tells the model about them
automatically when the session has connectors:

```bash theme={null}
nh list                                  what is connected
nh docs github create_issue              the tool's parameters
nh call github create_issue '{"owner":"acme","repo":"web","title":"Broken link"}'
```

<Note>
  `nh` exists only inside the sandbox. It is not the [CLI](/cli/nhar) — different program,
  different powers.
</Note>

## Outbound safety

Declared, not hoped for. `hosts` is enforced on every request, redirects are bounded, and
private address ranges are refused — so a connector cannot be aimed at the host it runs on, or
at your metadata service. A per-command call ceiling bounds a command that loops, because a
bug that makes unbounded requests on a customer's credential is an expensive one.

## Remote MCP servers

A remote MCP server is one kind of connector: `kind: "mcp"` with a `server_url`, its tools
discovered with `tools/list` rather than declared. The schema accepts and validates it today;
wiring it to the gateway is on the roadmap.

## What is yours

Storing credentials, the UI where a user connects an account, and which of your users may
attach what. The harness enforces the boundary once you have decided it.
