npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@agentpress/sdk

v0.15.0

Published

TypeScript SDK for the AgentPress API

Readme

@agentpress/sdk

TypeScript SDK for the AgentPress webhook API.

  • Package: @agentpress/sdk (npm, public)
  • Output: Dual ESM (.mjs) + CJS (.cjs)
  • Node: >= 22.0.0
  • Dependencies: jose for Partner MCP JWT verification; action webhook signing uses node:crypto

Installation

npm install @agentpress/sdk
# or
bun add @agentpress/sdk

Quick Start

import { AgentPress } from "@agentpress/sdk";

const client = new AgentPress({
  webhookSecret: "whsec_your_secret_here",
});

// Send a webhook
await client.webhooks.send({
  action: "my_action",
  payload: { eventType: "test", data: { key: "value" } },
});

Constructor Options

const client = new AgentPress({
  webhookSecret: "whsec_...",          // Required for default Svix sends, verify, and action management
  baseUrl: "https://api.agent.press",  // Default
  timeout: 30_000,                     // Default, in milliseconds
  org: "default-org",                  // Default org slug
  apiKey: "sk_...",                    // Optional, reserved for future API auth
  onRequest: (url, init) => {},        // Optional hook, called before every request
  onResponse: (url, response) => {},   // Optional hook, receives a cloned Response
});

All options are optional. webhookSecret is required when calling send with the default Svix signing mode, HMAC/shared-token sends without an auth override, verify, verifyOrThrow, constructEvent, or action management methods.

Validation rules:

  • webhookSecret must start with "whsec_" or a ConfigurationError is thrown
  • timeout must be a positive finite number
  • Trailing slashes on baseUrl are stripped automatically

API Reference

The client exposes client.webhooks, client.actions, client.userApprovals, and client.partners.

client.webhooks.send(params)

Sends an arbitrary action webhook payload. By default the SDK uses Svix-style signing with the client's webhookSecret, which matches the default verification scheme for new AgentPress action webhooks. Pass auth to match another verification scheme configured for the action webhook.

const response = await client.webhooks.send({
  action: "my_webhook_action",
  payload: {
    eventType: "order.completed",
    externalId: "order-456",
    data: { total: 99.99 },
  },
});

eventType is the canonical discriminator and is recommended for new integrations. Every payload must contain exactly one top-level, non-empty string discriminator: eventType or the compatibility alias event. AgentPress returns HTTP 400 when both keys, neither key, or an empty/non-string selected value is sent. For event-only inputs, AgentPress uses the event value as the canonical eventType for routing, persistence, and action creation while retaining the original request payload unchanged as rawPayload provenance.

An existing event-based sender can use this compatibility envelope:

{
  "event": "mir.created",
  "timestamp": "2026-07-10T12:00:00.000Z",
  "data": { "rating": 5 }
}

Endpoint: POST {baseUrl}/webhooks/actions/{org}/{action}

action is the action webhook identifier shown on the AgentPress webhook detail page. The older /webhooks/ingest/{org}/{identifier} listener endpoint is kept only for compatibility APIs; new integrations should use send().

Params (WebhookSendParams):

| Field | Type | Description | |-------|------|-------------| | action | string | Webhook action identifier (used in URL path) | | payload | Record<string, unknown> | Arbitrary JSON payload to sign and send | | auth | WebhookSendAuth | Optional verification override. Defaults to Svix with the client's webhookSecret. |

Returns (WebhookResponse):

{
  success: boolean;
  actionId?: string;         // ID of created or existing action
  alreadyExists?: boolean;   // Duplicate externalId
  skipped?: boolean;         // Compatibility field for skipped deliveries
  buffered?: boolean;        // HTTP 202: accepted but waiting on configuration
  eventId?: string;          // Buffered/skipped webhook_events row
  reason?: string;           // Machine-readable buffered/skipped reason
  data?: Record<string, unknown>;
}

Configuration/resolution gaps such as a missing action rule, disabled rule, or unresolved user return HTTP 202 with buffered: true instead of throwing. The delivery is persisted and appears in the Actions ledger for debugging and retry.

Compatibility notes:

  • Existing client.webhooks.send() callers continue to work for action webhooks using the default Svix verification scheme. No SDK method was removed.
  • If the webhook is configured for hmac_sha256, shared_token, or none, pass the matching auth option. Manual sender scripts must send the matching headers.
  • Existing listener aliases and /webhooks/ingest/{org}/{identifier} remain compatibility paths, but new integrations should use send() and /webhooks/actions/{org}/{action}.
  • 202 with buffered: true is accepted delivery without a created action yet; check response.buffered before using response.actionId.
  • For verificationScheme: "none", the URL is the credential and can rotate when the webhook is created, switched to none, or rotated. Copy the latest URL after those operations.

Throws: ConfigurationError (missing required secret/token), HttpError (non-2xx), TimeoutError

Signing details: Each request gets three headers automatically:

  • svix-id -- unique message ID (msg_<uuid>)
  • svix-timestamp -- Unix seconds
  • svix-signature -- v1,<base64 HMAC-SHA256>

The signature input is ${msgId}.${timestamp}.${body}.

await client.webhooks.send({
  action: "billing_events",
  auth: {
    scheme: "hmac_sha256",
    secret: process.env.AGENTPRESS_WEBHOOK_SECRET!,
  },
  payload: {
    eventType: "invoice.created",
    data: { invoiceId: "inv_123" },
  },
});

Auth schemes:

| Scheme | Params | Notes | |--------|--------|-------| | svix | { secret?, msgId?, timestamp? } | Adds svix-id, svix-timestamp, and svix-signature. Uses webhookSecret when secret is omitted. | | hmac_sha256 | { secret?, timestamp? } | Adds x-webhook-timestamp and x-webhook-signature using signHmacWebhookRequest. Uses webhookSecret when secret is omitted. | | shared_token | { token? } | Adds x-webhook-token. Uses webhookSecret when token is omitted. | | none | none | Sends no verification headers. Only use for capability-URL action webhooks intentionally configured with no header verification. |

For verificationScheme: "none", the action webhook URL itself is the credential. Creating, switching, or rotating such a webhook may mint a new unguessable webhookIdentifier; copy the latest URL from AgentPress before updating manual sender scripts.


client.webhooks.verify(params)

Verifies an inbound Svix webhook signature. Returns true if valid, false if invalid or expired.

const isValid = client.webhooks.verify({
  payload: rawRequestBody, // string or Buffer
  headers: {
    "svix-id": req.headers["svix-id"],
    "svix-timestamp": req.headers["svix-timestamp"],
    "svix-signature": req.headers["svix-signature"],
  },
});

if (!isValid) {
  return new Response("Unauthorized", { status: 401 });
}

Params (WebhookVerifyParams):

| Field | Type | Description | |-------|------|-------------| | payload | string \| Buffer | Raw request body | | headers["svix-id"] | string | Message ID header | | headers["svix-timestamp"] | string | Unix timestamp header | | headers["svix-signature"] | string | Signature header (may contain space-separated signatures) |

Verification rules:

  • Timestamp must be within 5 minutes of current time (replay protection)
  • Uses timing-safe comparison (timingSafeEqual)
  • Supports multiple space-separated signatures in the header (any match = valid)

Throws: ConfigurationError if webhookSecret was not provided


client.webhooks.verifyOrThrow(params)

Same as verify but throws WebhookSignatureError on invalid signature. Useful in middleware.

try {
  client.webhooks.verifyOrThrow({
    payload: rawBody,
    headers: {
      "svix-id": headers["svix-id"],
      "svix-timestamp": headers["svix-timestamp"],
      "svix-signature": headers["svix-signature"],
    },
  });
  // Signature is valid, proceed
} catch (error) {
  if (error instanceof WebhookSignatureError) {
    return new Response("Invalid signature", { status: 401 });
  }
}

client.webhooks.constructEvent(params)

Verify and parse an inbound webhook from AgentPress in one step. Combines signature verification with JSON parsing and returns a typed ActionCallbackPayload. This is the recommended way to handle incoming webhooks.

import { AgentPress, type ActionCallbackPayload } from "@agentpress/sdk";

const client = new AgentPress({ webhookSecret: "whsec_..." });

// Express example
app.post("/webhooks/agentpress", express.raw({ type: "application/json" }), (req, res) => {
  const event = client.webhooks.constructEvent({
    payload: req.body, // raw body string or Buffer
    headers: {
      "svix-id": req.headers["svix-id"] as string,
      "svix-timestamp": req.headers["svix-timestamp"] as string,
      "svix-signature": req.headers["svix-signature"] as string,
    },
  });

  console.log(event.status);        // "staged" | "approved" | "executing" | "completed" | ...
  console.log(event.eventType);     // "action.pending_approval" | "action.approved" | ...
  console.log(event.agentResponse);  // { text: "...", toolCalls: [...] }

  res.status(200).json({ received: true });
});

Params: Same as verify / verifyOrThrow (WebhookVerifyParams).

Returns (ActionCallbackPayload):

| Field | Type | Description | |-------|------|-------------| | actionId | string | Action identifier | | status | ActionStatus | "pending", "staged", "approved", "executing", "completed", etc. | | actionType | string | The action type (e.g., "order.created") | | eventType | ActionEventType | Public callback event type | | webhookAction | string \| null | Webhook action identifier used to create this action | | stagedToolCall | StagedToolCall \| null | Tool awaiting approval for action.pending_approval callbacks | | occurredAt | string | ISO 8601 timestamp for this callback event | | completedAt | string | Backwards-compatible ISO 8601 timestamp field | | resultSummary | string \| null | Human-readable execution summary when populated | | sourceData | Record<string, unknown> | Original data from the inbound webhook | | externalId | string \| null | External system identifier | | userId | string \| null | AgentPress user ID | | threadId | string \| null | Thread ID if a conversation was created | | agentResponse | AgentResponse | Agent's text response and tool call results | | errorMessage | string \| null | User-safe error message if the action failed | | errorDetails | { code, provider, recoverable, userMessage } \| null | Structured metadata for branching/logging without raw provider errors | | rejectionReason | string \| null | Reason if rejected by a human reviewer |

Throws: WebhookSignatureError (invalid/expired signature), ConfigurationError (missing secret), AgentPressError (invalid JSON)


client.actions.approve(actionId, params)

Approve a staged action, optionally modifying the tool call arguments before execution.

// Approve as-is
await client.actions.approve("act_123", {
  action: "my_webhook_action",
});

// Approve with modified arguments
await client.actions.approve("act_123", {
  action: "my_webhook_action",
  editedToolCall: {
    toolName: "sendEmail",
    arguments: { subject: "Updated subject" },
  },
});

Endpoint: POST {baseUrl}/webhooks/actions/{org}/{action}/manage/{actionId}/approve

Params:

| Field | Type | Description | |-------|------|-------------| | actionId | string | Action ID (first positional argument) | | params.action | string | Webhook action identifier (from callback's webhookAction field) | | params.editedToolCall | object (optional) | Modified tool call to use instead of the original |

Returns (ActionManageResponse):

{
  success: boolean;
  actionId: string;
  status: ActionStatus;
}

Throws: ConfigurationError (missing secret), HttpError (non-2xx), TimeoutError


client.actions.reject(actionId, params)

Reject a staged action with an optional reason.

await client.actions.reject("act_456", {
  action: "my_webhook_action",
  reason: "Insufficient context to proceed",
});

Endpoint: POST {baseUrl}/webhooks/actions/{org}/{action}/manage/{actionId}/reject

Params:

| Field | Type | Description | |-------|------|-------------| | actionId | string | Action ID (first positional argument) | | params.action | string | Webhook action identifier (from callback's webhookAction field) | | params.reason | string (optional) | Reason for rejection |

Returns: ActionManageResponse (same as approve)

Throws: ConfigurationError (missing secret), HttpError (non-2xx), TimeoutError


client.userApprovals.getWebhookMetadata(params)

Fetch webhook metadata and the ordered tool catalog used for per-user approval settings.

const metadata = await client.userApprovals.getWebhookMetadata({
  webhookIdentifier: "partner_actions_webhook",
});

console.log(metadata.actionWebhookId);
console.log(metadata.availableTools.map((tool) => tool.toolName));

Each availableTools item includes toolName, label, description, destructiveHint, and sensitiveHint. Use toolName from this response when rendering partner settings UIs; do not hardcode names such as review-reply tool slugs.

client.userApprovals.list(params)

Read approval rules for a webhook, optionally resolving an external auth user.

const { approvals } = await client.userApprovals.list({
  webhookIdentifier: "partner_actions_webhook",
  userId: "external_user_123",
  authProvider: "partner_auth",
});

If the external user has not authenticated into AgentPress yet, or the resolved user is not a member of the webhook's org, core returns HTTP 422 with code: "USER_NOT_PROVISIONED". Provision the user by sending them through the partner external-auth flow once, ensure they belong to the org, then retry.

client.userApprovals.create(params)

Create or upsert one stored approval rule. Current AgentPress core resolves actionWebhookId from webhookIdentifier, so partner apps do not need to store the webhook UUID.

const metadata = await client.userApprovals.getWebhookMetadata({
  webhookIdentifier: "partner_actions_webhook",
});
const selectedTool = getSelectedToolFromYourUi(metadata.availableTools);

await client.userApprovals.create({
  webhookIdentifier: metadata.webhookIdentifier,
  userId: "external_user_123",
  authProvider: "partner_auth",
  toolName: selectedTool.toolName,
  mode: "always_allow",
});

client.userApprovals.sync(params)

Safely sync an Allow/Ask settings UI for one external user and one webhook. Use this for default approval settings.

const metadata = await client.userApprovals.getWebhookMetadata({
  webhookIdentifier: "partner_actions_webhook",
});

const allowByToolName: Record<string, boolean> = getSettingsFromYourUi();

const result = await client.userApprovals.sync({
  webhookIdentifier: metadata.webhookIdentifier,
  userId: "external_user_123",
  authProvider: "partner_auth",
  rules: metadata.availableTools.map((tool) => ({
    toolName: tool.toolName,
    mode: allowByToolName[tool.toolName] ? "always_allow" : "ask",
  })),
});

console.log(result.preservedDenyTools);

sync is idempotent. mode: "always_allow" upserts an allow rule. mode: "ask" removes an existing allow rule for that listed tool. Existing always_deny rules are preserved and returned in preservedDenyTools; tools omitted from rules are untouched.


Error Handling

All errors extend AgentPressError. Import and use instanceof for specific handling.

import {
  AgentPress,
  AgentPressError,
  ConfigurationError,
  HttpError,
  TimeoutError,
  WebhookSignatureError,
} from "@agentpress/sdk";

try {
  await client.webhooks.send({ action: "test", payload: {} });
} catch (error) {
  if (error instanceof HttpError) {
    // Non-2xx response from the server
    console.log(error.statusCode);    // e.g. 400, 404, 500
    console.log(error.responseBody);  // Raw response text
    console.log(error.url);           // Full request URL
  } else if (error instanceof TimeoutError) {
    // Request exceeded the configured timeout
  } else if (error instanceof ConfigurationError) {
    // Missing webhookSecret, invalid timeout, etc.
  } else if (error instanceof WebhookSignatureError) {
    // Only from verifyOrThrow -- invalid or expired signature
  } else if (error instanceof AgentPressError) {
    // Base class: fetch failures, unexpected non-JSON responses
  }
}

Error hierarchy:

AgentPressError (base)
  ConfigurationError      -- invalid options or missing webhookSecret / partnerMcp
  HttpError               -- non-2xx response (has statusCode, responseBody, url)
  TimeoutError            -- request exceeded timeout
  WebhookSignatureError   -- invalid/expired signature (from verifyOrThrow only)
  PartnerTokenError       -- Partner MCP JWT verification failure (has typed `reason`)
  KeyRotationVerifyError  -- Key-rotation webhook verification failure (has typed `reason`)

Exported Values and Types

import { ACTION_EVENT_TYPES } from "@agentpress/sdk";
import type {
  ActionCallbackPayload,         // Inbound webhook payload from constructEvent()
  ActionEventType,               // "action.pending_approval" | "action.approved" | ...
  ActionManageResponse,          // Response from actions.approve() / actions.reject()
  ActionStatus,                  // "pending" | "staged" | "approved" | "executing" | "rejected" | "completed" | "failed" | "expired"
  AgentPressOptions,             // Constructor options
  AgentResponse,                 // Agent text response + tool calls
  ApprovalMode,                  // "always_allow" | "always_deny"
  ApprovalRuleSyncMode,          // "always_allow" | "ask"
  ApproveActionParams,           // actions.approve() params
  CreateUserApprovalParams,      // userApprovals.create() params
  DeleteUserApprovalParams,      // userApprovals.delete() params
  GetUserApprovalWebhookMetadataParams, // userApprovals.getWebhookMetadata() params
  KeyRotationEvent,              // Verified signing_key_rotation webhook payload
  KeyRotationVerifyErrorReason,  // Union of KeyRotationVerifyError.reason values
  KeyRotationVerifyParams,       // verifyKeyRotation() params
  ListUserApprovalsParams,       // userApprovals.list() params
  ListUserApprovalsResponse,     // userApprovals.list() response
  PartnerMcpOptions,             // AgentPressOptions.partnerMcp shape
  PartnerTokenClaims,            // Verified Partner MCP Spec v1 JWT claims
  PartnerTokenErrorReason,       // Union of PartnerTokenError.reason values
  RejectActionParams,            // actions.reject() params
  StagedToolCall,                // Tool call awaiting approval
  StagedToolCallSummary,         // Structured staged tool summary
  SyncUserApprovalRule,          // One tool rule for userApprovals.sync()
  SyncUserApprovalsParams,       // userApprovals.sync() params
  SyncUserApprovalsResponse,     // userApprovals.sync() response
  ToolCallResult,                // Individual tool call (name, arguments, result)
  UpdateUserApprovalParams,      // userApprovals.update() params
  UserToolApproval,              // Persisted user approval row
  WebhookResponse,               // Response from send
  WebhookApprovalMetadata,        // userApprovals.getWebhookMetadata() response
  WebhookApprovalToolMetadata,    // One ordered tool metadata row
  WebhookSendParams,             // send params
  WebhookVerifyParams,           // verify / verifyOrThrow / constructEvent params
} from "@agentpress/sdk";

Full Example: Receiving Action Callbacks

import express from "express";
import { AgentPress, WebhookSignatureError, type ActionCallbackPayload } from "@agentpress/sdk";

const app = express();
const client = new AgentPress({ webhookSecret: process.env.WEBHOOK_SECRET });

// Recommended: use constructEvent() for verify + parse in one step
app.post("/webhooks/agentpress", express.raw({ type: "application/json" }), (req, res) => {
  let event: ActionCallbackPayload;
  try {
    event = client.webhooks.constructEvent({
      payload: req.body,
      headers: {
        "svix-id": req.headers["svix-id"] as string,
        "svix-timestamp": req.headers["svix-timestamp"] as string,
        "svix-signature": req.headers["svix-signature"] as string,
      },
    });
  } catch (error) {
    if (error instanceof WebhookSignatureError) {
      return res.status(401).json({ error: "Invalid signature" });
    }
    throw error;
  }

  // event is fully typed as ActionCallbackPayload
  console.log(`Action ${event.actionId} ${event.status}`);
  if (event.agentResponse.text) {
    console.log("Agent said:", event.agentResponse.text);
  }

  res.json({ received: true });
});

Partner MCP Spec v1

Partners building an MCP server that federates with AgentPress can use the SDK's built-in verifiers for the two cryptographic primitives the spec requires: EdDSA JWT verification (against a remote JWKS) for inbound bearer tokens, and HMAC-SHA256 webhook verification for signing_key_rotation notifications.

This consolidates ~200 lines of hand-rolled JWT/JWKS/HMAC code that would otherwise live in every partner MCP integration. For the full wire protocol see the Partner MCP Spec v1 guide.

Configuration

Partner MCP helpers require a partnerMcp block on the constructor. All fields except clockTolerance and jwksCacheMaxAgeMs are required.

import { AgentPress } from "@agentpress/sdk";

// Per-org as of Partner MCP Spec v1 — each AgentPress org that registers you
// as a partner has its own signing keypair and JWKS URL. The org admin who
// registers you will share the org slug; your `issuer` + `jwksUrl` are
// derived from it.
const client = new AgentPress({
  webhookSecret: "whsec_...",            // Needed for verifyKeyRotation
  partnerMcp: {
    jwksUrl: "https://api.agent.press/orgs/<org-slug>/.well-known/jwks.json",
    issuer: "https://api.agent.press/orgs/<org-slug>",
    audience: "https://mcp.example.com", // Your MCP's stable URL
    expectedExtProvider: "partner_auth", // Required ext_provider claim value
    clockTolerance: "30s",               // Default: "30s" (accepts number of seconds or jose duration string)
    jwksCacheMaxAgeMs: 3_600_000,        // Default: 1 hour
  },
});

Running staging and production side-by-side

Staging and production MUST be separate AgentPress client instances, each with its own partnerMcp block. JWKS cache state is per-instance (not module-global), so two clients can run side-by-side in the same process without cross-talk.

const staging = new AgentPress({
  webhookSecret: process.env.AGENTPRESS_WEBHOOK_SECRET_STAGING,
  partnerMcp: {
    jwksUrl: "https://stg-api.agent.press/orgs/<org-slug>/.well-known/jwks.json",
    issuer: "https://stg-api.agent.press/orgs/<org-slug>",
    audience: "https://mcp-staging.example.com",
    expectedExtProvider: "partner_auth",
  },
});

const production = new AgentPress({
  webhookSecret: process.env.AGENTPRESS_WEBHOOK_SECRET_PROD,
  partnerMcp: {
    jwksUrl: "https://api.agent.press/orgs/<org-slug>/.well-known/jwks.json",
    issuer: "https://api.agent.press/orgs/<org-slug>",
    audience: "https://mcp.example.com",
    expectedExtProvider: "partner_auth",
  },
});

Route requests to the correct client by inspecting the JWT's iss claim (or by a request-scoped environment flag upstream of the verifier). If the same partner integration serves multiple AgentPress orgs, spin up one client per org — do NOT share a single client across orgs (JWKS caches, iss, and aud are all org-scoped).


client.partners.verifyToken(token)

Verifies an EdDSA JWT issued by AgentPress against the configured remote JWKS. Returns typed PartnerTokenClaims on success; throws PartnerTokenError with a typed reason on failure.

Enforces the full spec: alg pinned to EdDSA, iss / aud exact-match, ext_provider exact-match, sub / jti / scope required and non-empty, kid header required, exp / iat validated with configurable skew (default ±30s).

import { AgentPress, PartnerTokenError } from "@agentpress/sdk";
import { Hono } from "hono";

const client = new AgentPress({ partnerMcp: { /* ... */ } });

const app = new Hono();

app.post("/mcp", async (c) => {
  const auth = c.req.header("authorization") ?? "";
  const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";

  if (!token) {
    return c.text("Unauthorized", 401, {
      "WWW-Authenticate": 'Bearer error="invalid_token", error_description="Missing bearer token"',
    });
  }

  try {
    const claims = await client.partners.verifyToken(token);

    // claims is typed PartnerTokenClaims -- use `sub` to resolve the external user
    // into your local user record (partner-domain logic):
    const user = await resolveExternalUser(claims.ext_provider, claims.sub);
    c.set("user", user);
    c.set("claims", claims);

    return handleMcpRequest(c);
  } catch (err) {
    if (err instanceof PartnerTokenError) {
      const description = mapReasonToDescription(err.reason);
      return c.text("Unauthorized", 401, {
        "WWW-Authenticate": `Bearer error="invalid_token", error_description="${description}"`,
      });
    }
    throw err;
  }
});

Error handling table — all PartnerTokenError reasons map to RFC 6750 error="invalid_token". Use error_description to distinguish:

| reason | HTTP | error | error_description | |---|---|---|---| | signature_invalid | 401 | invalid_token | Signature verification failed | | issuer_mismatch | 401 | invalid_token | Issuer does not match | | audience_mismatch | 401 | invalid_token | Audience does not match | | expired | 401 | invalid_token | Token expired | | not_yet_valid | 401 | invalid_token | Token not yet valid | | missing_claim | 401 | invalid_token | Required claim missing or invalid | | ext_provider_mismatch | 401 | invalid_token | ext_provider does not match | | algorithm_not_allowed | 401 | invalid_token | Algorithm not allowed | | kid_missing_or_unknown | 401 | invalid_token | Signing key id missing or unknown | | malformed | 401 | invalid_token | Malformed token |


client.webhooks.verifyKeyRotation(params) + client.partners.refreshJwks()

Verifies a signing_key_rotation webhook signed by AgentPress with HMAC-SHA256 over the raw request body, then refreshes the JWKS cache so the next verified token picks up the new signing key without a failed-verify penalty.

The rotation webhook handler is optional — partners who don't implement it still pick up rotations automatically on the next kid miss (with one failed verify latency). Implement it to make rotations seamless.

import { AgentPress, KeyRotationVerifyError } from "@agentpress/sdk";
import { Hono } from "hono";

const client = new AgentPress({
  webhookSecret: process.env.AGENTPRESS_WEBHOOK_SECRET,
  partnerMcp: { /* ... */ },
});

const app = new Hono();

app.post("/webhooks/agentpress/key-rotation", async (c) => {
  // IMPORTANT: read the raw body, not c.req.json() -- HMAC is computed over
  // the exact bytes AgentPress sent, not a re-serialized JSON object.
  const rawBody = await c.req.text();

  // Headers are passed verbatim as Record<string, string | undefined>; casing
  // is handled internally.
  const headers: Record<string, string | undefined> = {};
  c.req.raw.headers.forEach((value, key) => {
    headers[key] = value;
  });

  try {
    const event = client.webhooks.verifyKeyRotation({
      payload: rawBody,
      headers,
    });

    // event is typed KeyRotationEvent
    console.log(
      `Rotation (${event.type}): ${event.retiredKid} -> ${event.newCurrentKid}`,
    );

    // Force-refetch the JWKS so the next verifyToken() picks up the new key.
    await client.partners.refreshJwks();

    return c.json({ received: true });
  } catch (err) {
    if (err instanceof KeyRotationVerifyError) {
      const status = mapRotationReasonToStatus(err.reason);
      return c.text(err.message, status);
    }
    throw err;
  }
});

Error handling table for KeyRotationVerifyError reasons:

| reason | HTTP | Notes | |---|---|---| | invalid_signature | 401 | HMAC mismatch | | invalid_signature_format | 401 | x-agentpress-signature missing or not sha256=<hex> | | timestamp_out_of_window | 401 | x-agentpress-timestamp outside ±5 min tolerance | | invalid_timestamp | 401 | x-agentpress-timestamp missing or not a unix-seconds integer | | malformed_payload | 400 | Body is not valid JSON or doesn't match the rotation event schema | | payload_too_large | 413 | Body exceeds 8 KB cap |


For the full Partner MCP Spec v1 — token shape, JWKS discovery, rotation semantics, and error protocol — see the guide on docs.agentpress.dev.

File Structure

packages/sdk/src/
  index.ts              -- Public barrel export
  client.ts             -- AgentPress class, option validation + resolution
  http.ts               -- HttpClient (fetch wrapper with timeout, hooks, error parsing)
  errors.ts             -- Error class hierarchy
  types.ts              -- All exported types and interfaces
  utils.ts              -- randomMessageId() helper (msg_<uuid>)
  actions/
    client.ts           -- ActionsClient (approve, reject)
  partners/
    client.ts           -- PartnersClient (verifyToken, refreshJwks)
  webhooks/
    client.ts           -- WebhooksClient (send, verify, verifyOrThrow, constructEvent, verifyKeyRotation)
    signing.ts          -- HMAC-SHA256 Svix-compatible sign + verify functions
    keyRotation.ts      -- Partner MCP Spec v1 rotation webhook HMAC + timestamp + payload validation