Skip to content
Felspar
Esc
↑↓navigate↵open⌘Jpreview
On this page

Claude Agent SDK

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.

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

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:

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 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:

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.

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). Its draft/ modules may change between minor versions until 1.0.

Was this page helpful?