(
project_slug: string,
catalog_key: string,
method: string,
params: Record<string, unknown> = {},
opts: McpRpcOpts = {},
)
| 177 | * is returned and the caller's existing stale-token UX kicks in. |
| 178 | */ |
| 179 | export async function mcpRpcAutoRefresh<T = unknown>( |
| 180 | project_slug: string, |
| 181 | catalog_key: string, |
| 182 | method: string, |
| 183 | params: Record<string, unknown> = {}, |
| 184 | opts: McpRpcOpts = {}, |
| 185 | ): Promise<RpcResult<T>> { |
| 186 | const spec = mcpSpecByKey(project_slug, catalog_key); |
| 187 | if (!spec) { |
| 188 | return { ok: false, kind: "http_error", status: 404, body: "unknown mcp catalog key" }; |
| 189 | } |
| 190 | let token = findMcpToken(project_slug, catalog_key); |
| 191 | if (!token) { |
| 192 | return { ok: false, kind: "http_error", status: 401, body: "no token stored" }; |
| 193 | } |
| 194 | |
| 195 | if (isExpiringSoon(token)) { |
| 196 | const refreshed = await refreshMcpToken(token); |
| 197 | if (refreshed) token = refreshed; |
| 198 | } |
| 199 | |
| 200 | const first = await mcpRpc<T>( |
| 201 | spec.resource_url, |
| 202 | token.access_token_enc, |
| 203 | method, |
| 204 | params, |
| 205 | opts, |
| 206 | ); |
| 207 | if (first.ok) return first; |
| 208 | if (first.kind !== "http_error" || first.status !== 401) return first; |
| 209 | |
| 210 | // Reactive refresh. Skip if we just refreshed proactively — the upstream |
| 211 | // is telling us our fresh token is bad, and looping would burn cycles. |
| 212 | // `updated_at` is bumped by updateMcpTokenSecrets, so we can detect a |
| 213 | // refresh that happened within this same call. |
| 214 | if (Date.parse(token.updated_at) > Date.now() - 5_000) return first; |
| 215 | |
| 216 | const refreshed = await refreshMcpToken(token); |
| 217 | if (!refreshed) return first; |
| 218 | return mcpRpc<T>(spec.resource_url, refreshed.access_token_enc, method, params, opts); |
| 219 | } |
| 220 | |
| 221 | function parseRpcBody<T>(body: string): RpcResult<T> { |
| 222 | const envelope = pickRpcEnvelope(body); |
no test coverage detected