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.
103 lines • 4.33 kB
JavaScript
/** CI-session credential helpers for OIDC-backed deploy flows. */
import { LocalError } from "./errors.js";
import { buildClient } from "./kernel.js";
import { Ci } from "./namespaces/ci.js";
import { CI_AUDIENCE, } from "./namespaces/ci.types.js";
export const CI_SESSION_CREDENTIALS = Symbol.for("@run402/sdk/ci-session-credentials");
export function isCiSessionCredentials(credentials) {
return Boolean(credentials[CI_SESSION_CREDENTIALS]);
}
export function createCiSessionCredentials(opts) {
if (!opts?.projectId) {
throw new LocalError("createCiSessionCredentials requires projectId", "creating CI session credentials");
}
if (!opts.accessToken && !opts.getAccessToken) {
throw new LocalError("createCiSessionCredentials requires accessToken or getAccessToken", "creating CI session credentials");
}
const provider = {
async getAuth() {
const token = opts.getAccessToken ? await opts.getAccessToken() : opts.accessToken;
if (!token) {
throw new LocalError("CI session credentials did not return an access token", "authenticating with CI session");
}
return { Authorization: `Bearer ${token}` };
},
async getProject(id) {
if (id !== opts.projectId)
return null;
return { anon_key: "", service_key: "" };
},
async getActiveProject() {
return opts.projectId;
},
};
Object.defineProperty(provider, CI_SESSION_CREDENTIALS, {
value: true,
enumerable: false,
});
return provider;
}
export function githubActionsCredentials(opts) {
if (!opts?.projectId) {
throw new LocalError("githubActionsCredentials requires projectId", "creating GitHub Actions CI credentials");
}
const apiBase = opts.apiBase ?? CI_AUDIENCE;
const audience = opts.audience ?? CI_AUDIENCE;
const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
const refreshBeforeMs = Math.max(0, opts.refreshBeforeSeconds ?? 60) * 1000;
let cached = null;
return createCiSessionCredentials({
projectId: opts.projectId,
getAccessToken: async () => {
const now = Date.now();
if (cached && now < cached.refreshAtMs)
return cached.token;
const subjectToken = await requestGithubOidcToken(fetchImpl, audience);
const exchanged = await exchangeWithRun402Ci(fetchImpl, apiBase, opts.projectId, subjectToken);
cached = {
token: exchanged.access_token,
refreshAtMs: now + Math.max(0, exchanged.expires_in * 1000 - refreshBeforeMs),
};
return cached.token;
},
});
}
async function requestGithubOidcToken(fetchImpl, audience) {
const env = getProcessEnv();
const requestUrl = env.ACTIONS_ID_TOKEN_REQUEST_URL;
const requestToken = env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
if (!requestUrl || !requestToken) {
throw new LocalError("GitHub Actions OIDC environment is missing ACTIONS_ID_TOKEN_REQUEST_URL or ACTIONS_ID_TOKEN_REQUEST_TOKEN. Ensure the workflow has permissions: id-token: write.", "requesting GitHub Actions OIDC token");
}
const url = new URL(requestUrl);
url.searchParams.set("audience", audience);
const res = await fetchImpl(url.toString(), {
headers: { Authorization: `Bearer ${requestToken}` },
});
const body = await res.json().catch(() => null);
if (!res.ok || typeof body?.value !== "string" || body.value.length === 0) {
throw new LocalError(`GitHub Actions OIDC token request failed (HTTP ${res.status})`, "requesting GitHub Actions OIDC token");
}
return body.value;
}
async function exchangeWithRun402Ci(fetchImpl, apiBase, projectId, subjectToken) {
const noAuth = {
async getAuth() {
return null;
},
async getProject() {
return null;
},
};
const ci = new Ci(buildClient({
apiBase,
fetch: fetchImpl,
credentials: noAuth,
}));
return ci.exchangeToken({ project_id: projectId, subject_token: subjectToken });
}
function getProcessEnv() {
const proc = globalThis;
return proc.process?.env ?? {};
}
//# sourceMappingURL=ci-credentials.js.map