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.
65 lines • 2.89 kB
JavaScript
/**
* `writeApproval` — the passkey-signed, target-scoped credential a person
* signed in on the command line needs to provision, deploy, or write secrets
* without a wallet. Reached as `r.writeApproval.*`.
*
* The ceremony: {@link WriteApproval.requestChallenge} opens a passkey approval
* scoped to one `(action, target)` and returns a same-origin `confirm_url`; the
* person approves with their passkey in the browser; the confirm page redirects
* to the CLI's `127.0.0.1` loopback with a one-time `code`;
* {@link WriteApproval.exchangeClaimCode} (PKCE) mints the token. The token is
* sent as `X-Run402-Write-Approval: Bearer <write_approval_token>` beside the
* sign-in session bearer, and dies with that session. A write approval never
* satisfies step-up.
*
* Isomorphic seams — the loopback server and PKCE generation live in the Node
* CLI (`run402 approve`).
*/
import { gateSecret } from "../secret-gate.js";
export class WriteApproval {
client;
constructor(client) {
this.client = client;
}
/**
* Open a scoped approval (`POST /agent/v1/control-plane/write-approval/challenges`).
* Carries the sign-in session bearer; a `device`-grade session is refused.
*/
async requestChallenge(input) {
gateSecret(this.client, "writeApproval.requestChallenge", input);
const body = {
action: input.action,
cli_redirect_uri: input.cliRedirectUri,
code_challenge: input.codeChallenge,
state: input.state,
};
if (input.orgId !== undefined)
body.org_id = input.orgId;
if (input.projectId !== undefined)
body.project_id = input.projectId;
return this.client.request("/agent/v1/control-plane/write-approval/challenges", {
method: "POST",
body,
...(input.token
? { headers: { Authorization: `Bearer ${input.token}` }, withAuth: false }
: {}),
context: "requesting a write approval challenge",
});
}
/**
* Exchange the loopback claim `code` (+ PKCE verifier + `state`) for the
* write-approval token (`POST …/write-approval/cli/token`). No `redirect_uri`
* — it is bound at challenge time. Unauthenticated (code + verifier are the
* credential); the minted token is still bound to the issuing sign-in session.
*/
async exchangeClaimCode(input) {
gateSecret(this.client, "writeApproval.exchangeClaimCode", input);
return this.client.request("/agent/v1/control-plane/write-approval/cli/token", {
method: "POST",
body: { code: input.code, code_verifier: input.codeVerifier, state: input.state },
withAuth: false,
context: "exchanging a write approval claim code",
});
}
}
//# sourceMappingURL=write-approval.js.map