---
title: Claude Agent SDK
description: Connect an agent built with the Claude Agent SDK to Felspar, install the Felspar skill, and gate its acting tools with PreToolUse and PostToolUse hooks.
sidebar:
  order: 5
---

An agent built with Anthropic's Claude Agent SDK connects to Felspar as an MCP server with its own agent key. The pre-action gate plugs in as `PreToolUse` and `PostToolUse` hooks. The example on this page reviews incoming NDAs for a legal team.

## Connect

```ts
import { query } from "@anthropic-ai/claude-agent-sdk";

const felspar = {
  type: "http",
  url: "https://mcp.felspar.ai/mcp",
  headers: { Authorization: `Bearer ${process.env.FELSPAR_AGENT_KEY}` },
} as const;

for await (const message of query({
  prompt: "Review contract CTR-2026-0117 with the Felspar job review-nda.",
  options: {
    mcpServers: { felspar },
    allowedTools: [
      "mcp__felspar__list_jobs",
      "mcp__felspar__get_job_requirements",
      "mcp__felspar__get_job_decision",
      "mcp__felspar__check_output",
    ],
  },
})) {
  // handle messages
}
```

The agent sees Felspar's four agent tools as `mcp__felspar__<tool>`. Keep the key in the environment, never in code or chat.

## Add the skill

`@felspar/sdk` ships the Felspar method as a skill. Write it into your project's `.claude` directory, where Claude Code keeps project skills, or put its text in the system prompt:

```sh
npx @felspar/sdk skill .claude    # writes .claude/skills/felspar/SKILL.md
```

Pass `--force` to replace an existing copy. The skill is the same for every job: pick the job with `list_jobs` if none is named, read the requirements once, send exact quotes with the case's ID (and related records' IDs, such as the contract's counterparty), act only on Felspar's outcome, hand off by `escalation.id` when the result carries one, and check any written output with `check_output`. See [Eve: add the skill](/integrations/eve#add-the-skill) for a summary.

## Add the pre-action gate

Tool names in this runtime are `mcp__<server>__<tool>`, with the server name from `mcpServers`. Here the contracts system's "mark ready to sign" tool is allowed only for the `sign` outcome, and only for the contract Felspar decided on:

```ts
import { createActionGate, felsparJobDescriber, felsparVerifier } from "@felspar/sdk/draft/action-gate";
import { felsparHooks } from "@felspar/sdk/draft/action-gate/claude-agent-sdk";

const felsparApi = { origin: "https://api.felspar.ai", agentKey: process.env.FELSPAR_AGENT_KEY };

const gate = createActionGate({
  job: "review-nda",
  decisionTool: "mcp__felspar__get_job_decision",
  outputCheckTool: "mcp__felspar__check_output",
  tools: {
    mcp__contracts__mark_ready_to_sign: {
      // An action that writes no declared output: list the outcomes that allow it.
      outcomes: ["sign"],
      // The case is the contract in this field: the decision's entity_id, exactly.
      entity: { field: "contractId" },
      fields: ["contractId", "note"],
    },
  },
  verify: felsparVerifier(felsparApi),
  describe: felsparJobDescriber(felsparApi),
});

for await (const message of query({
  prompt,
  options: { mcpServers: { felspar, contracts }, hooks: felsparHooks(gate) },
})) {
  // ...
}
```

For a tool that writes text the pack declares as an output (a reply, a note to the business owner), use `writes: "<output id>"` and `outputCheck: { fields: [...] }` instead of `outcomes`, so the pack decides which outcomes may write it and Felspar must have passed the exact text. See [The pre-action gate](/guides/pre-action-gate#tool-rules).

`felsparHooks(gate)` adds:

- a **`PostToolUse`** hook that records each `get_job_decision` result with the `entity_id` it asked about, and each `check_output` result, exactly as Felspar returned them;
- a **`PreToolUse`** hook that runs the gate before a gated tool. A denied call is denied with the reason and, when the policy gives one, `Next step:` with the company's own next step, which the model sees.

## How the hook behaves

- **It only denies.** An allowed call gets no decision from the hook, so the session's own permission rules, modes and prompts still apply. An "allow" from the gate never skips them.
- **Don't pair it with hooks that rewrite gated tools' input.** Another `PreToolUse` hook that returns `updatedInput` could change a call after the gate checked it.
- Decisions are kept per session (`session_id`), so one conversation's decision never authorizes another's action.

If you already have hooks, add `felsparPreToolUse(gate)` and `felsparPostToolUse(gate)` to your existing matchers instead.

## Notes

- The adapters use structural types and add no dependencies. They were type-checked against `@anthropic-ai/claude-agent-sdk` 0.3.
- The gate needs `@felspar/sdk` 0.2.0 or later, and 0.2.1 or later for jobs whose case IDs Felspar issues or derives ([identity modes](/concepts/subjects-and-escalations#identity-modes)). Its `draft/` modules may change between minor versions until 1.0.
