Claude Agent SDK
Give a Claude Agent SDK agent your workspace's actions as tools, have an approver decide gated ones in Anlyon, and keep provider credentials out of the agent's process
The Claude Agent SDK (@anthropic-ai/claude-agent-sdk for TypeScript, claude-agent-sdk for Python) takes MCP servers as a tool source. Anlyon’s MCP server works as one: the agent sees your workspace’s actions as tools, calls one, and Anlyon executes it against production systems.
The statements about the Claude Agent SDK on this page come from Anthropic’s documentation as checked on 2026-10-18. Check Anthropic’s permissions, MCP and custom tools pages for the current behaviour.
Your agent declares what it wants to do. Anlyon executes it against production systems. The agent names an action and passes input, and Anlyon resolves the credentials at dispatch and makes the request. That is true of actions routed through Anlyon’s hosted executor. It is not true of a tool your own code calls directly, which is not routed through Anlyon and is not governed by it.
When the SDK’s own approval is enough
The SDK can pause a tool call for approval. A canUseTool callback on query() receives the tool name and input and returns allow or deny. As of 2026-10-18, it fires only for calls that no hook, deny rule, permission mode or allow rule resolved first, and a PreToolUse hook runs before every call. Both run in your process, and the tool then runs with whatever credential your process holds.
Use the SDK’s callback when the user in front of the agent is the reviewer. Use an Anlyon action when the decision belongs to someone else, when you need a record, a shared limit across sessions and agents, or a provider key that stays out of the agent’s process.
Create the agent key
An agent built on the SDK runs without a browser, so start the self-hosted server as a stdio process with an API key. The hosted https://api.anlyon.com/mcp accepts OAuth access tokens issued through “Login with Anlyon” only.
Create two keys in the dashboard:
- Agent key (
ANLYON_AGENT_KEY):actions:read,actions:invoke,approvals:read. This is the key the MCP server acts with. Do not give itapprovals:decide. - Operator key (
ANLYON_OPERATOR_KEY):approvals:decideandactions:read, held by the person or service that reviews. A key can never decide an approval it requested itself. Addactions:resolveif this key will also record the outcome of anunknowninvocation.
Connect the Anlyon MCP server
Pass the server in mcpServers. The SDK names each MCP tool mcp__<server>__<tool>, where <server> is the key you give it. Every enabled action is also an MCP tool named action_<name>, so an action named refund is mcp__anlyon__action_refund here.
// Needs ANTHROPIC_API_KEY and ANLYON_AGENT_KEY.
import { query } from '@anthropic-ai/claude-agent-sdk';
for await (const message of query({
prompt:
'Refund payment pi_3P9x for order ord_1042, 24000 cents in usd, with action_refund. ' +
'If it is parked for approval, call wait_for_approval with the approvalId and report the decision.',
options: {
mcpServers: {
anlyon: {
command: 'npx',
args: ['-y', '@anlyonhq/mcp-server'],
env: { ANLYON_API_KEY: process.env.ANLYON_AGENT_KEY! },
},
},
// The approval happens in Anlyon, so the SDK does not prompt for these two tools.
allowedTools: ['mcp__anlyon__action_refund', 'mcp__anlyon__wait_for_approval'],
permissionMode: 'default',
},
})) {
if (message.type === 'result' && message.subtype === 'success') console.log(message.result);
}
List the Anlyon tools by name in allowedTools. An allowed tool runs without a canUseTool prompt, which would otherwise put a second approval, one Anlyon does not record, in front of the one that counts. Leave a tool out of allowedTools if you want the user in front of the agent to confirm it as well.
The Python SDK takes the same server as ClaudeAgentOptions(mcp_servers=..., allowed_tools=[...]).
What happens
The action refund in this example uses the stripe.refund adapter with requiresApproval: true. See Governed effects to define it.
- The agent calls
action_refund. Anlyon records the invocation, creates an approval, and returnsAwaiting human approvalwith itsapr_...id. Nothing has been sent to Stripe. - The agent calls
wait_for_approval, which holds the call open (60 seconds by default, 120 at most) until an approver decides. If it comes back stillpending, the agent calls it again. - An approver approves in the Anlyon inbox, or with the operator key:
curl -sS -X POST "${ANLYON_BASE_URL:-https://api.anlyon.com}/api/v2/approvals/$APPROVAL_ID/approve" \
-H "Authorization: Bearer $ANLYON_OPERATOR_KEY" \
-H "Content-Type: application/json" \
-d '{"note": "Checked the order; refund is correct."}'
- The human approves the exact change, and that exact change is what runs. The approval is bound to a digest of the request, target, versions, preconditions and expiry, and Anlyon recomputes it before dispatch and refuses on any difference. Stripe has no compare-and-set, so the refundable amount shown is as of review.
- Anlyon reserves the refund against any impact limit that applies, attaches the Stripe key from the vault, and sends the refund. Every agent, session and key in the environment draws from the same limit. A new session or key does not get a new allowance.
wait_for_approvalreturns the approval record. The agent reads the decision (approved,deniedorexpired) and who made it.payload.invocationIdnames the invocation it gated. A denied or expired approval comes back with that status, and the gated request is never sent.
Approval requests notify over Slack, email, Discord or a webhook. The decision happens in the dashboard or through the API.
Read the outcome
The approval says who decided. The invocation says what happened when Anlyon made the request, and for a governed action it carries a receipt grade. Read it with any key that has actions:read:
curl -sS "${ANLYON_BASE_URL:-https://api.anlyon.com}/api/v2/actions/invocations/$INVOCATION_ID" \
-H "Authorization: Bearer $ANLYON_OPERATOR_KEY"
| Grade | Meaning |
|---|---|
confirmed |
A read-back matched the request. |
acknowledged |
The provider accepted the request. Nothing read it back. |
unknown |
No usable response. Anlyon does not send it again. It stays unknown until a read-back or an operator settles it. |
failed |
The provider rejected the request, or it never left Anlyon. |
refused |
Anlyon refused it at dispatch. |
denied |
The approval was denied. |
pending |
No receipt yet. |
For stripe.refund, every success is read from Stripe’s own record of the refund. See Receipts and grades.
Anlyon does not retry an action invocation. Where a request may have reached the destination and the outcome cannot be confirmed, the invocation is recorded as unknown and you reconcile before retrying. See When the outcome is unknown.
Call Anlyon from a custom tool instead
To control the wait and the text the agent reads, define your own tool with the SDK’s tool() and createSdkMcpServer() and call the Anlyon SDK from its handler:
import { tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
import { Client } from '@anlyonhq/sdk';
import { z } from 'zod';
const anlyon = new Client({ apiKey: process.env.ANLYON_AGENT_KEY! });
const refundOrder = tool(
'refund_order',
'Refund a Stripe payment. Amount is in cents. Every refund waits for an approver.',
{ orderId: z.string(), paymentIntent: z.string(), amount: z.number().int() },
async ({ orderId, paymentIntent, amount }) => {
const { data } = await anlyon.actions.invoke(
'refund',
{ paymentIntent, amount, currency: 'usd' },
{ operationKey: `refund:${orderId}` },
);
const text = data!.pendingApproval
? `Waiting for an approver (${data!.approvalId}). Do not retry.`
: `Status ${data!.status}, grade ${data!.grade}.`;
return { content: [{ type: 'text', text }] };
},
);
export const billing = createSdkMcpServer({ name: 'billing', version: '1.0.0', tools: [refundOrder] });
Pass it as mcpServers: { billing } and allow mcp__billing__refund_order. The handler returns as soon as the approval is created. Approving it is what dispatches, so the agent does not have to be running when the approver decides.
Credentials
The model never sees a credential: an action references {{secret:NAME}} and Anlyon resolves it at dispatch. The secret is bound to a destination and a placement, checked again at fire time. This covers credentials in the Anlyon vault, not a key your own process already holds.
The agent’s process still holds the Anlyon agent key and your Anthropic key. The SDK runs Claude Code’s built-in tools, including Bash and Read, unless you remove them with the tools or disallowedTools options. A key in the agent’s environment is reachable by those tools, so keep provider keys in the vault and give the agent key only the scopes above.
More than one approver
To require more than one decision, set an approval policy on the action. N counts distinct credentials for API-key deciders and distinct users for dashboard and OAuth deciders: two keys held by one person are two deciders.

