run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
576 lines • 29 kB
JavaScript
import { LocalError } from "../errors.js";
import { waitFor } from "../wait.js";
const SECRET_FIELD = /(?:private.?key|secret|mnemonic|seed|derivation|nsec|nostr.?key|service.?key|grant.?key|recovery.?code)/i;
function rejectSecrets(value, path = "$", seen = new Set()) {
if (!value || typeof value !== "object")
return;
if (seen.has(value))
return;
seen.add(value);
if (Array.isArray(value)) {
value.forEach((entry, index) => rejectSecrets(entry, `${path}[${index}]`, seen));
return;
}
for (const [key, entry] of Object.entries(value)) {
if (SECRET_FIELD.test(key)) {
throw new LocalError("Buzz private keys, credentials, and recovery material are never accepted", "preparing Buzz control-plane request", {
code: "BUZZ_SECRET_INPUT_FORBIDDEN",
details: { field: `${path}.${key}` },
});
}
rejectSecrets(entry, `${path}.${key}`, seen);
}
}
function required(value, field, context) {
if (!value || typeof value !== "string")
throw new LocalError(`${field} is required`, context, { details: { field } });
return value;
}
function headers(idempotencyKey) {
return { "Idempotency-Key": idempotencyKey ?? globalThis.crypto.randomUUID() };
}
export class BuzzHumanAdoptions {
client;
constructor(client) {
this.client = client;
}
async create(input) {
rejectSecrets(input);
return this.client.request("/buzz-human-adoptions/v1", {
method: "POST",
headers: headers(input.idempotencyKey),
body: {
org_id: required(input.organizationId, "organizationId", "starting Buzz human adoption"),
identity_link_id: required(input.identityLinkId, "identityLinkId", "starting Buzz human adoption"),
},
context: "starting Buzz human adoption",
});
}
async list(organizationId) {
const response = await this.client.request(`/buzz-human-adoptions/v1?org_id=${encodeURIComponent(required(organizationId, "organizationId", "listing Buzz human adoptions"))}`, { context: "listing Buzz human adoptions" });
return response.buzz_human_adoptions ?? [];
}
async get(id) {
return this.client.request(`/buzz-human-adoptions/v1/${encodeURIComponent(required(id, "buzzHumanAdoptionId", "reading Buzz human adoption"))}`, { context: "reading Buzz human adoption" });
}
async complete(id, ownerProofEvent, idempotencyKey) {
rejectSecrets(ownerProofEvent);
return this.client.request(`/buzz-human-adoptions/v1/${encodeURIComponent(required(id, "buzzHumanAdoptionId", "completing Buzz human adoption"))}/complete`, {
method: "POST",
headers: headers(idempotencyKey),
body: { owner_proof_event: ownerProofEvent },
context: "completing Buzz human adoption",
});
}
async cancel(id, idempotencyKey) {
return this.client.request(`/buzz-human-adoptions/v1/${encodeURIComponent(required(id, "buzzHumanAdoptionId", "cancelling Buzz human adoption"))}`, {
method: "DELETE",
headers: headers(idempotencyKey),
context: "cancelling Buzz human adoption",
});
}
}
/** Durable agent-created HTTPS offer pages. Creating an offer is inert; the human
* receives authority only after a separately authenticated, passkey-stepped-up
* browser attempt completes with the exact Buzz proof. */
export class BuzzHumanAdoptionOffers {
client;
constructor(client) {
this.client = client;
}
async create(input) {
rejectSecrets(input);
return this.client.request("/buzz-human-adoption-offers/v1", {
method: "POST",
headers: headers(input.idempotencyKey),
body: {
org_id: required(input.organizationId, "organizationId", "creating Buzz human-adoption offer"),
identity_link_id: required(input.identityLinkId, "identityLinkId", "creating Buzz human-adoption offer"),
...(input.deploymentContext ? { deployment_context: input.deploymentContext } : {}),
},
context: "creating Buzz human-adoption offer",
});
}
async get(id) {
return this.client.request(`/buzz-human-adoption-offers/v1/${encodeURIComponent(required(id, "buzzHumanAdoptionOfferId", "reading Buzz human-adoption offer"))}`, {
context: "reading Buzz human-adoption offer",
});
}
async cancel(id, idempotencyKey) {
return this.client.request(`/buzz-human-adoption-offers/v1/${encodeURIComponent(required(id, "buzzHumanAdoptionOfferId", "cancelling Buzz human-adoption offer"))}`, {
method: "DELETE",
headers: headers(idempotencyKey),
context: "cancelling Buzz human-adoption offer",
});
}
async createAttempt(id, input) {
rejectSecrets(input);
return this.client.request(`/buzz-human-adoption-offers/v1/${encodeURIComponent(required(id, "buzzHumanAdoptionOfferId", "creating Buzz human-adoption attempt"))}/attempts`, {
method: "POST",
headers: headers(input.idempotencyKey),
body: { callback_url: required(input.callbackUrl, "callbackUrl", "creating Buzz human-adoption attempt") },
context: "creating Buzz human-adoption attempt",
});
}
}
export class BuzzCommunityInstallations {
client;
constructor(client) {
this.client = client;
}
async create(input) {
rejectSecrets(input);
return this.client.request("/buzz-community-installations/v1", {
method: "POST",
headers: headers(input.idempotencyKey),
body: {
org_id: required(input.organizationId, "organizationId", "starting Buzz community installation"),
buzz_community_subject: required(input.buzzCommunitySubject, "buzzCommunitySubject", "starting Buzz community installation"),
...(input.buzzCommunityAuthoritySubject ? { buzz_community_authority_subject: input.buzzCommunityAuthoritySubject } : {}),
...(input.enrollmentPolicy ? { enrollment_policy: input.enrollmentPolicy } : {}),
},
context: "starting Buzz community installation",
});
}
async list(organizationId) {
const response = await this.client.request(`/buzz-community-installations/v1?org_id=${encodeURIComponent(required(organizationId, "organizationId", "listing Buzz community installations"))}`, { context: "listing Buzz community installations" });
return response.buzz_community_installations ?? [];
}
async get(id) {
return this.client.request(`/buzz-community-installations/v1/${encodeURIComponent(required(id, "buzzCommunityInstallationId", "reading Buzz community installation"))}`, { context: "reading Buzz community installation" });
}
/**
* The invite front door: `invite` is the one-use relay invite a Buzz
* community owner or admin minted in Buzz Desktop — the bare code, an
* `https://<relay>/invite/<code>` link, or a `buzz://join?…` link. Run402
* claims it as the installation identity; no Nostr key leaves Desktop.
*/
async activate(id, invite, idempotencyKey) {
return this.client.request(`/buzz-community-installations/v1/${encodeURIComponent(required(id, "buzzCommunityInstallationId", "activating Buzz community installation"))}/activate`, {
method: "POST",
headers: headers(idempotencyKey),
body: { invite: required(invite, "invite", "activating Buzz community installation") },
context: "activating Buzz community installation",
});
}
/**
* The teammate door (`POST /buzz-community-installations/v1/:id/teammates`):
* a Buzz-launched agent presents the NIP-OA owner attestation Buzz injected
* as `BUZZ_AUTH_TAG` and, when the installation's policy opens the door
* (`owner_attested_agents: "developer"`) and the attesting owner is a
* current community owner/admin, joins the installed organization as a
* developer under its own principal. 201 on join, 200 when already a
* teammate. The attestation is public data (owner pubkey + signature).
*/
async joinAsTeammate(id, input) {
rejectSecrets(input);
return this.client.request(`/buzz-community-installations/v1/${encodeURIComponent(required(id, "buzzCommunityInstallationId", "joining Buzz organization as teammate"))}/teammates`, {
method: "POST",
headers: headers(input.idempotencyKey),
body: {
identity_link_id: required(input.identityLinkId, "identityLinkId", "joining Buzz organization as teammate"),
owner_attestation: Array.isArray(input.ownerAttestation)
? input.ownerAttestation
: required(input.ownerAttestation, "ownerAttestation", "joining Buzz organization as teammate"),
},
context: "joining Buzz organization as teammate",
});
}
async update(id, input) {
rejectSecrets(input);
return this.client.request(`/buzz-community-installations/v1/${encodeURIComponent(required(id, "buzzCommunityInstallationId", "updating Buzz community installation"))}`, {
method: "PATCH",
headers: headers(input.idempotencyKey),
body: {
default_for_enrollment: input.defaultForEnrollment,
enrollment_policy: input.enrollmentPolicy,
policy_revision: input.policyRevision,
},
context: "updating Buzz community installation",
});
}
async revoke(id, idempotencyKey) {
return this.client.request(`/buzz-community-installations/v1/${encodeURIComponent(required(id, "buzzCommunityInstallationId", "revoking Buzz community installation"))}`, {
method: "DELETE",
headers: headers(idempotencyKey),
context: "revoking Buzz community installation",
});
}
async discoverPublicDescriptors(buzzCommunitySubject) {
const response = await this.client.request(`/buzz-community-installation-descriptors/v1?buzz_community_subject=${encodeURIComponent(required(buzzCommunitySubject, "buzzCommunitySubject", "discovering public Buzz community descriptors"))}`, { withAuth: false, context: "discovering public Buzz community descriptors" });
return response.buzz_community_installation_descriptors ?? [];
}
async getPublicDescriptor(id) {
return this.client.request(`/buzz-community-installation-descriptors/v1/${encodeURIComponent(required(id, "buzzCommunityInstallationId", "reading public Buzz community descriptor"))}`, {
withAuth: false,
context: "reading public Buzz community descriptor",
});
}
}
export class BuzzAgentEnrollments {
client;
constructor(client) {
this.client = client;
}
async request(input) {
rejectSecrets(input);
return this.client.request("/buzz-agent-enrollments/v1", {
method: "POST",
headers: headers(input.idempotencyKey),
body: {
buzz_community_installation_id: required(input.buzzCommunityInstallationId, "buzzCommunityInstallationId", "requesting Buzz agent enrollment"),
identity_link_id: required(input.identityLinkId, "identityLinkId", "requesting Buzz agent enrollment"),
requested_grants: input.requestedGrants,
expires_at: required(input.expiresAt, "expiresAt", "requesting Buzz agent enrollment"),
...(input.ownerAttestation !== undefined ? { owner_attestation: input.ownerAttestation } : {}),
},
context: "requesting Buzz agent enrollment",
});
}
async list(options = {}) {
const query = new URLSearchParams();
if (options.organizationId)
query.set("org_id", options.organizationId);
if (options.status)
query.set("status", options.status);
const suffix = query.size ? `?${query.toString()}` : "";
const response = await this.client.request(`/buzz-agent-enrollments/v1${suffix}`, { context: "listing Buzz agent enrollments" });
return response.buzz_agent_enrollments ?? [];
}
async get(id) {
return this.client.request(`/buzz-agent-enrollments/v1/${encodeURIComponent(required(id, "buzzAgentEnrollmentId", "reading Buzz agent enrollment"))}`, { context: "reading Buzz agent enrollment" });
}
async approve(id, input) {
rejectSecrets(input);
return this.client.request(`/buzz-agent-enrollments/v1/${encodeURIComponent(required(id, "buzzAgentEnrollmentId", "approving Buzz agent enrollment"))}/approve`, {
method: "POST",
headers: headers(input.idempotencyKey),
body: {
approved_grants: input.approvedGrants,
installation_descriptor_revision: input.installationDescriptorRevision,
installation_policy_revision: input.installationPolicyRevision,
},
context: "approving Buzz agent enrollment",
});
}
async deny(id, reason, idempotencyKey) {
return this.client.request(`/buzz-agent-enrollments/v1/${encodeURIComponent(required(id, "buzzAgentEnrollmentId", "denying Buzz agent enrollment"))}/deny`, {
method: "POST",
headers: headers(idempotencyKey),
body: reason === undefined ? {} : { reason },
context: "denying Buzz agent enrollment",
});
}
async revoke(id, idempotencyKey) {
return this.client.request(`/buzz-agent-enrollments/v1/${encodeURIComponent(required(id, "buzzAgentEnrollmentId", "revoking or cancelling Buzz agent enrollment"))}`, {
method: "DELETE",
headers: headers(idempotencyKey),
context: "revoking or cancelling Buzz agent enrollment",
});
}
}
/** A delivery that will never change again. Everything else is still in flight. */
const TERMINAL_DELIVERY_STATUSES = new Set([
"delivered",
"dead_letter",
"cancelled",
"suppressed",
]);
function routePath(routeId, context) {
return `/buzz-project-event-routes/v1/${encodeURIComponent(required(routeId, "routeId", context))}`;
}
/**
* `buzz.notifications` — project-event routing into a Buzz community channel
* (gateway `add-buzz-project-event-routing`).
*
* A route is an owner-declared destination: one active community
* installation, an explicit 1–50 project scope, reviewed event filters, one
* NIP-29 channel. Run402's project-event feed stays authoritative — Buzz
* relay state never creates, acknowledges, or advances a Run402 event, and
* Buzz is NEVER a deadman channel: mandatory notification classes keep their
* human paths regardless of route state.
*
* The workflow is **configure → authorize → test → live**:
*
* ```ts
* const created = await r.buzz.notifications.createRoute(orgId, {
* installationId: "buzzci_…",
* routeName: "deploys",
* buzzChannelId: "<nip-29 channel id>",
* projectIds: ["prj_…"],
* });
* if (created.authorization.status === "pending_buzz_authorization") {
* // The ONE non-secret handoff: a Buzz community owner adds
* // created.authorization.notification_pubkey as a relay member
* // (created.authorization.instructions says exactly how), then:
* const delivery = await r.buzz.notifications.testAndWait(
* created.buzz_project_event_route_id,
* );
* // delivered ⇒ live. Still queued ⇒ the tick publishes ~every 60s —
* // silence here is cadence, not failure; keep polling deliveries.
* }
* ```
*
* Load-bearing semantics:
* - Only three reviewed event types are routable (`deploy_activated`,
* `error_fingerprints_observed`, `platform_incident`); the classes
* `security` / `billing_critical` / `destructive_lifecycle` /
* `verification` / `recovery` may never be routed.
* - Filters: omitted/`null` = everything registered; an explicit `[]` is a
* 422, never a wildcard and never a silent match-nothing.
* - Every mutation carries an `Idempotency-Key` (auto-generated when not
* given) and requires fresh `buzz.event_route` step-up server-side (a SIWX
* wallet is inherently fresh).
* - No response ever contains the signing secret — `notification_pubkey` +
* `signing_generation` are the only credential material on the wire.
* - Routes deliver NEW events only (`start_after_event_id` floor); delivery
* is at-least-once with byte-identical republish, backing off to
* `dead_letter` after 8 attempts / 48h — visible in `deliveries()`.
*/
export class BuzzNotifications {
client;
constructor(client) {
this.client = client;
}
/**
* Create a route (`POST /buzz-project-event-routes/v1`, 201). The response
* carries an `authorization` block: the installation's first route is
* `pending_buzz_authorization` with the exact non-secret connect handoff a
* Buzz community owner completes; later routes on an already-authorized
* installation come back `authorized` and active.
*/
async createRoute(organizationId, input) {
const context = "creating Buzz event route";
rejectSecrets(input);
const orgWide = input?.projectScope === "org";
if (!orgWide && (!Array.isArray(input?.projectIds) || input.projectIds.length === 0)) {
throw new LocalError("projectIds must name at least one project — a listed route's scope is explicit (pass projectScope: \"org\" for the whole organization)", context, {
details: { field: "projectIds" },
});
}
return this.client.request("/buzz-project-event-routes/v1", {
method: "POST",
headers: headers(input.idempotencyKey),
body: {
org_id: required(organizationId, "organizationId", context),
buzz_community_installation_id: required(input.installationId, "installationId", context),
route_name: required(input.routeName, "routeName", context),
buzz_channel_id: required(input.buzzChannelId, "buzzChannelId", context),
...(input.projectIds !== undefined ? { project_ids: input.projectIds } : {}),
...(input.projectScope !== undefined ? { project_scope: input.projectScope } : {}),
...(input.includeOrgEvents !== undefined ? { include_org_events: input.includeOrgEvents } : {}),
...(input.eventTypes !== undefined ? { event_types: input.eventTypes } : {}),
...(input.eventClasses !== undefined ? { event_classes: input.eventClasses } : {}),
...(input.onCallBuzzPubkey !== undefined ? { on_call_buzz_pubkey: input.onCallBuzzPubkey } : {}),
...(input.onCallDisplayName !== undefined ? { on_call_display_name: input.onCallDisplayName } : {}),
},
context,
});
}
/** List the organization's routes, including retained revoked ones. */
async list(organizationId) {
const response = await this.client.request(`/buzz-project-event-routes/v1?org_id=${encodeURIComponent(required(organizationId, "organizationId", "listing Buzz event routes"))}`, { context: "listing Buzz event routes" });
return response.buzz_project_event_routes ?? [];
}
/**
* Route detail: the route plus honest `health` (derived from route +
* credential state, never queue emptiness), `delivery_counts`, the oldest
* pending time, and the shared `consumer_cursor`.
*/
async get(routeId) {
const context = "reading Buzz event route";
return this.client.request(routePath(routeId, context), { context });
}
/**
* Update name / channel / project scope / filters
* (`PATCH /:id` with `expected_revision`). A stale revision fails
* `409 BUZZ_ROUTE_REVISION_STALE` without mutating — re-read, re-decide,
* re-send with the current `revision`.
*/
async update(routeId, patch, expectedRevision) {
const context = "updating Buzz event route";
rejectSecrets(patch);
if (!Number.isInteger(expectedRevision)) {
throw new LocalError("expectedRevision must be the route's current integer revision", context, {
details: { field: "expectedRevision" },
});
}
return this.client.request(routePath(routeId, context), {
method: "PATCH",
headers: headers(patch.idempotencyKey),
body: {
expected_revision: expectedRevision,
...(patch.routeName !== undefined ? { route_name: patch.routeName } : {}),
...(patch.buzzChannelId !== undefined ? { buzz_channel_id: patch.buzzChannelId } : {}),
...(patch.projectIds !== undefined ? { project_ids: patch.projectIds } : {}),
...(patch.projectScope !== undefined ? { project_scope: patch.projectScope } : {}),
...(patch.eventTypes !== undefined ? { event_types: patch.eventTypes } : {}),
...(patch.eventClasses !== undefined ? { event_classes: patch.eventClasses } : {}),
...(patch.includeOrgEvents !== undefined ? { include_org_events: patch.includeOrgEvents } : {}),
...(patch.onCallBuzzPubkey !== undefined ? { on_call_buzz_pubkey: patch.onCallBuzzPubkey } : {}),
...(patch.onCallDisplayName !== undefined ? { on_call_display_name: patch.onCallDisplayName } : {}),
},
context,
});
}
/**
* Stop matching new events (`POST /:id/pause`). Non-terminal deliveries
* freeze; events occurring while paused are never retroactively delivered.
*/
async pause(routeId, idempotencyKey) {
const context = "pausing Buzz event route";
return this.client.request(`${routePath(routeId, context)}/pause`, {
method: "POST",
headers: headers(idempotencyKey),
body: {},
context,
});
}
/**
* Re-arm delivery and reset the hard-failure counter (`POST /:id/resume`).
* Requires a live signing credential NOW — resuming into
* signing-unavailable fails `503 BUZZ_NOTIFICATION_SIGNING_UNAVAILABLE`
* rather than fabricating an "active" route whose every attempt fails.
*/
async resume(routeId, idempotencyKey) {
const context = "resuming Buzz event route";
return this.client.request(`${routePath(routeId, context)}/resume`, {
method: "POST",
headers: headers(idempotencyKey),
body: {},
context,
});
}
/**
* Revoke (`DELETE /:id`): queued deliveries are cancelled, sanitized history
* stays readable, and `notification_credential_destroyed` reports whether
* this was the installation's last live route (only then does its
* notification credential die — never out from under a sibling).
*/
async revoke(routeId, idempotencyKey) {
const context = "revoking Buzz event route";
return this.client.request(routePath(routeId, context), {
method: "DELETE",
headers: headers(idempotencyKey),
context,
});
}
/**
* Stage the NEXT signing generation (`POST /:id/rotate`, 202). The current
* key keeps signing; the swap activates only after the next pubkey's own
* Buzz-side NIP-43 membership verifies — the `rotation` block carries the
* next pubkey, the authorize hint, and the `verify_path` to poke.
*/
async rotate(routeId, idempotencyKey) {
const context = "rotating Buzz notification credential";
return this.client.request(`${routePath(routeId, context)}/rotate`, {
method: "POST",
headers: headers(idempotencyKey),
body: {},
context,
});
}
/**
* Queue one signed test delivery (`POST /:id/test`, 202 + Retry-After).
* On a `pending_authorization` route this FIRST re-checks the Buzz-side
* NIP-43 membership and activates the route when it landed — the test
* endpoint doubles as the authorization poll. The 202 is queued-not-
* delivered (Faithful): poll `poll.path` (or use {@link testAndWait}).
*/
async test(routeId, idempotencyKey) {
const context = "creating Buzz route test delivery";
return this.client.request(`${routePath(routeId, context)}/test`, {
method: "POST",
headers: headers(idempotencyKey),
body: {},
context,
});
}
/** Keyset newest-first delivery history — dead letters included, the signed envelope never. */
async deliveries(routeId, opts = {}) {
const context = "listing Buzz route deliveries";
const params = new URLSearchParams();
if (opts.limit !== undefined)
params.set("limit", String(opts.limit));
if (opts.cursor)
params.set("cursor", opts.cursor);
if (opts.deliveryId)
params.set("delivery_id", opts.deliveryId);
const query = params.size ? `?${params.toString()}` : "";
return this.client.request(`${routePath(routeId, context)}/deliveries${query}`, {
context,
});
}
/**
* Queue a test delivery, then poll it until it settles — the whole
* verify-the-route flow in one call.
*
* Returns as soon as the delivery reaches a terminal status
* (`delivered` | `dead_letter` | `cancelled` | `suppressed`). On timeout
* the still-queued delivery is RETURNED, never thrown (the shared `waitFor`
* contract) — and unlike an unanswered escalation, a not-yet-delivered test
* here is usually just cadence: the single-consumer tick publishes ~every
* 60s, so keep polling `deliveries()` before concluding anything is wrong.
*/
async testAndWait(routeId, opts = {}) {
const queued = await this.test(routeId);
// The 202 is the FIRST observed state — it is the only one carrying the
// poll block, so an observer that narrates (the CLI) must see it even
// when the wait settles instantly.
opts.onPoll?.(queued);
if (TERMINAL_DELIVERY_STATUSES.has(queued.status))
return queued;
const deliveryId = queued.buzz_project_event_delivery_id;
const { state } = await waitFor(async () => {
const page = await this.deliveries(routeId, { deliveryId, limit: 1 });
return page.buzz_project_event_deliveries?.[0] ?? queued;
}, (d) => TERMINAL_DELIVERY_STATUSES.has(d.status), {
pollMs: Math.max(opts.pollMs ?? 5_000, 2_000),
timeoutMs: opts.timeoutMs ?? 3 * 60 * 1000,
onPoll: opts.onPoll ? (s) => opts.onPoll(s) : undefined,
});
return state;
}
}
export class Buzz {
client;
humanAdoptions;
humanAdoptionOffers;
communityInstallations;
enrollments;
notifications;
constructor(client) {
this.client = client;
this.humanAdoptions = new BuzzHumanAdoptions(client);
this.humanAdoptionOffers = new BuzzHumanAdoptionOffers(client);
this.communityInstallations = new BuzzCommunityInstallations(client);
this.enrollments = new BuzzAgentEnrollments(client);
this.notifications = new BuzzNotifications(client);
}
/** Capability-detecting status. Older gateways return a safe supported:false result. */
async status() {
const whoami = await this.client.request("/agent/v1/whoami", { context: "reading Buzz control-plane status" });
const buzz = whoami.buzz;
if (!buzz || typeof buzz !== "object" || Array.isArray(buzz)) {
return { supported: false, protocol: "run402.buzz-control-plane.v1", reason: "gateway_not_supported", buzz: null, whoami };
}
return { supported: true, protocol: "run402.buzz-control-plane.v1", buzz: buzz, whoami };
}
/** Goal-shaped alias for the canonical adoption initiation. */
adopt(input) {
return this.humanAdoptions.create(input);
}
/** Create the canonical conversational HTTPS offer without starting consent. */
offerAdoption(input) {
return this.humanAdoptionOffers.create(input);
}
/** Goal-shaped alias for the canonical community installation initiation. */
install(input) {
return this.communityInstallations.create(input);
}
/** Goal-shaped alias for the canonical agent enrollment request. */
enroll(input) {
return this.enrollments.request(input);
}
}
//# sourceMappingURL=buzz.js.map