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.
81 lines • 3.33 kB
JavaScript
/**
* `secrets` namespace — project-scoped environment variables for functions.
* All methods require the project's service key.
*/
import { LocalError } from "../errors.js";
import { requireProjectCredentials } from "../project-credentials.js";
const SECRET_KEY_RE = /^[A-Z_][A-Z0-9_]{0,127}$/;
const SECRET_VALUE_LIMIT_BYTES = 4 * 1024;
export class Secrets {
client;
constructor(client) {
this.client = client;
}
/** Set or overwrite a project secret. Injected as `process.env.KEY` in deployed functions. */
async set(projectId, key, opts) {
const value = opts.value;
validateSecretKey(key, "setting secret");
validateSecretValue(value);
const project = await requireProjectCredentials(this.client, projectId, "setting secret");
await this.client.request(`/projects/v1/admin/${projectId}/secrets`, {
method: "POST",
headers: { Authorization: `Bearer ${project.service_key}` },
body: { key, value },
context: "setting secret",
});
}
/** List secret keys for a project. Values and value-derived hashes are never returned. */
async list(projectId) {
const project = await requireProjectCredentials(this.client, projectId, "listing secrets");
const body = await this.client.request(`/projects/v1/admin/${projectId}/secrets`, {
headers: { Authorization: `Bearer ${project.service_key}` },
context: "listing secrets",
});
return normalizeSecretList(body);
}
/** Delete a secret. */
async delete(projectId, key) {
const project = await requireProjectCredentials(this.client, projectId, "deleting secret");
return this.client.request(`/projects/v1/admin/${projectId}/secrets/${encodeURIComponent(key)}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${project.service_key}` },
context: "deleting secret",
});
}
}
function validateSecretKey(key, context) {
if (typeof key !== "string" || !SECRET_KEY_RE.test(key)) {
throw new LocalError(`Secret key must match ${SECRET_KEY_RE.source}`, context);
}
}
function validateSecretValue(value) {
if (typeof value !== "string") {
throw new LocalError("Secret value must be a string", "setting secret");
}
const bytes = new TextEncoder().encode(value).byteLength;
if (bytes > SECRET_VALUE_LIMIT_BYTES) {
throw new LocalError(`Secret value is ${bytes} bytes; maximum is ${SECRET_VALUE_LIMIT_BYTES} UTF-8 bytes`, "setting secret");
}
}
function normalizeSecretList(body) {
const raw = Array.isArray(body)
? body
: body && typeof body === "object" && Array.isArray(body.secrets)
? body.secrets
: [];
const secrets = raw.flatMap((item) => {
if (!item || typeof item !== "object")
return [];
const obj = item;
if (typeof obj.key !== "string")
return [];
const out = { key: obj.key };
if (typeof obj.created_at === "string")
out.created_at = obj.created_at;
if (typeof obj.updated_at === "string")
out.updated_at = obj.updated_at;
return [out];
});
return { secrets };
}
//# sourceMappingURL=secrets.js.map