Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
64 lines
2.5 KiB
TypeScript
64 lines
2.5 KiB
TypeScript
import type { McpServerConfigInput } from "@rakazo/contracts";
|
|
|
|
/** Shape of the encrypted MCP credential blob. `oauth` holds SDK OAuth state
|
|
* (tokens, client registration, PKCE verifier) managed by McpOAuthBroker. */
|
|
export type McpSecretMaterial = {
|
|
secret?: string;
|
|
env?: Record<string, string>;
|
|
headers?: Record<string, string>;
|
|
oauth?: Record<string, unknown>;
|
|
};
|
|
|
|
export type McpMaterialUpdate =
|
|
| { action: "keep" }
|
|
| { action: "store"; material: McpSecretMaterial };
|
|
|
|
/** Compute the next encrypted credential blob for an MCP server update.
|
|
*
|
|
* - "keep": the update carries no credential data; leave the stored blob as is.
|
|
* - "store": rewrite the blob. An empty material means credentials were
|
|
* cleared entirely — the caller should delete the secret row and null the
|
|
* server's secretId instead of storing an empty object.
|
|
*
|
|
* env/headers use full-replace semantics (the update payload is the complete
|
|
* set), matching the create handler. OAuth state is preserved unless the
|
|
* endpoint changed, since tokens issued for one endpoint must never be sent to
|
|
* another server. */
|
|
export function buildMcpUpdateMaterial(
|
|
existing: McpSecretMaterial,
|
|
config: McpServerConfigInput,
|
|
options: { clearOAuth?: boolean } = {},
|
|
): McpMaterialUpdate {
|
|
const material = options.clearOAuth ? { ...existing } : existing;
|
|
const clearedOAuth = options.clearOAuth === true && material.oauth !== undefined;
|
|
if (options.clearOAuth) delete material.oauth;
|
|
const clearing = config.clearCredential === true;
|
|
if (clearing) {
|
|
return { action: "store", material: material.oauth ? { oauth: material.oauth } : {} };
|
|
}
|
|
const secret = "secret" in config && config.secret ? config.secret : undefined;
|
|
const env = "env" in config ? config.env : undefined;
|
|
const headers = "headers" in config ? config.headers : undefined;
|
|
const existingHasMaterial = Boolean(
|
|
material.secret ||
|
|
(material.env && Object.keys(material.env).length > 0) ||
|
|
(material.headers && Object.keys(material.headers).length > 0) ||
|
|
material.oauth,
|
|
);
|
|
const suppliesMaterial = Boolean(
|
|
secret || (env && Object.keys(env).length > 0) || (headers && Object.keys(headers).length > 0),
|
|
);
|
|
if (!existingHasMaterial && !suppliesMaterial) {
|
|
return clearedOAuth ? { action: "store", material } : { action: "keep" };
|
|
}
|
|
return {
|
|
action: "store",
|
|
material: {
|
|
...material,
|
|
...(secret ? { secret } : {}),
|
|
...(env !== undefined ? { env } : {}),
|
|
...(headers !== undefined ? { headers } : {}),
|
|
},
|
|
};
|
|
}
|