@tempo-ai/mcp 0.0.102-staging.2 → 0.0.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2 -2
- package/dist/{call-JRAJWRA2.js → call-TNF2AXMX.js} +3 -3
- package/dist/{chunk-2ZXV6PYD.js → chunk-6P4PI7CS.js} +6 -6
- package/dist/{chunk-J43EDR63.js → chunk-KQNQSVOA.js} +12 -5
- package/dist/{chunk-J43EDR63.js.map → chunk-KQNQSVOA.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/{serve-PQ3MLJHP.js → serve-NGUFKD5I.js} +375 -141
- package/dist/serve-NGUFKD5I.js.map +1 -0
- package/dist/{token-VESH5QAF.js → token-IDV6YJ2Q.js} +2 -2
- package/package.json +1 -1
- package/dist/serve-PQ3MLJHP.js.map +0 -1
- /package/dist/{call-JRAJWRA2.js.map → call-TNF2AXMX.js.map} +0 -0
- /package/dist/{chunk-2ZXV6PYD.js.map → chunk-6P4PI7CS.js.map} +0 -0
- /package/dist/{token-VESH5QAF.js.map → token-IDV6YJ2Q.js.map} +0 -0
package/dist/bin.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
run
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-6P4PI7CS.js";
|
|
6
|
+
import "./chunk-KQNQSVOA.js";
|
|
7
7
|
|
|
8
8
|
// src/bin.ts
|
|
9
9
|
run(process.argv).then(
|
|
@@ -2,13 +2,13 @@ import { createRequire as __tempoCreateRequire } from 'node:module'; const requi
|
|
|
2
2
|
import {
|
|
3
3
|
ensureCliAuth,
|
|
4
4
|
parseServeArgs
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KQNQSVOA.js";
|
|
6
6
|
|
|
7
7
|
// src/call.ts
|
|
8
8
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
9
9
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
10
10
|
async function connectAggregate(options) {
|
|
11
|
-
const { createTempoAggregate } = await import("./serve-
|
|
11
|
+
const { createTempoAggregate } = await import("./serve-NGUFKD5I.js");
|
|
12
12
|
const handle = await createTempoAggregate(options);
|
|
13
13
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
14
14
|
const client = new Client({ name: "tempo-mcp-cli", version: "0" });
|
|
@@ -127,4 +127,4 @@ export {
|
|
|
127
127
|
runCall,
|
|
128
128
|
runTools
|
|
129
129
|
};
|
|
130
|
-
//# sourceMappingURL=call-
|
|
130
|
+
//# sourceMappingURL=call-TNF2AXMX.js.map
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
parseServeArgs,
|
|
7
7
|
readAuth,
|
|
8
8
|
startBrowserAuthFlow
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KQNQSVOA.js";
|
|
10
10
|
|
|
11
11
|
// src/help.ts
|
|
12
12
|
var USAGE = `tempo-mcp <subcommand>
|
|
@@ -157,7 +157,7 @@ async function run(argv) {
|
|
|
157
157
|
process.stderr.write(USAGE);
|
|
158
158
|
return 1;
|
|
159
159
|
}
|
|
160
|
-
const { runServe } = await import("./serve-
|
|
160
|
+
const { runServe } = await import("./serve-NGUFKD5I.js");
|
|
161
161
|
return await runServe(options);
|
|
162
162
|
}
|
|
163
163
|
switch (subcommand) {
|
|
@@ -168,15 +168,15 @@ async function run(argv) {
|
|
|
168
168
|
case "whoami":
|
|
169
169
|
return await runWhoami();
|
|
170
170
|
case "token": {
|
|
171
|
-
const { runToken } = await import("./token-
|
|
171
|
+
const { runToken } = await import("./token-IDV6YJ2Q.js");
|
|
172
172
|
return await runToken(rest);
|
|
173
173
|
}
|
|
174
174
|
case "tools": {
|
|
175
|
-
const { runTools } = await import("./call-
|
|
175
|
+
const { runTools } = await import("./call-TNF2AXMX.js");
|
|
176
176
|
return await runTools(rest);
|
|
177
177
|
}
|
|
178
178
|
case "call": {
|
|
179
|
-
const { runCall } = await import("./call-
|
|
179
|
+
const { runCall } = await import("./call-TNF2AXMX.js");
|
|
180
180
|
return await runCall(rest);
|
|
181
181
|
}
|
|
182
182
|
default:
|
|
@@ -195,4 +195,4 @@ async function run(argv) {
|
|
|
195
195
|
export {
|
|
196
196
|
run
|
|
197
197
|
};
|
|
198
|
-
//# sourceMappingURL=chunk-
|
|
198
|
+
//# sourceMappingURL=chunk-6P4PI7CS.js.map
|
|
@@ -858,7 +858,17 @@ function sleep3(ms) {
|
|
|
858
858
|
// src/cli-auth.ts
|
|
859
859
|
async function ensureCliAuth() {
|
|
860
860
|
const accessToken = envAccessToken();
|
|
861
|
+
const convexSiteUrl = resolveConvexSiteUrl(resolveConvexUrl());
|
|
861
862
|
if (!accessToken) {
|
|
863
|
+
const existing2 = await readAuth();
|
|
864
|
+
if (existing2 && existing2.expiresAt - Date.now() <= 6e4) {
|
|
865
|
+
const loop = createRefreshLoop({
|
|
866
|
+
convexSiteUrl,
|
|
867
|
+
onReauthRequired: () => {
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
await loop.refreshNow();
|
|
871
|
+
}
|
|
862
872
|
await ensureAuth();
|
|
863
873
|
return;
|
|
864
874
|
}
|
|
@@ -866,10 +876,7 @@ async function ensureCliAuth() {
|
|
|
866
876
|
if (existing && storedAuthMatchesAccessToken(existing, accessToken) && existing.expiresAt - Date.now() > 6e4) {
|
|
867
877
|
return;
|
|
868
878
|
}
|
|
869
|
-
await exchangeAccessToken({
|
|
870
|
-
convexSiteUrl: resolveConvexSiteUrl(resolveConvexUrl()),
|
|
871
|
-
accessToken
|
|
872
|
-
});
|
|
879
|
+
await exchangeAccessToken({ convexSiteUrl, accessToken });
|
|
873
880
|
}
|
|
874
881
|
function envTokenReauth(convexSiteUrl) {
|
|
875
882
|
return async () => {
|
|
@@ -920,4 +927,4 @@ export {
|
|
|
920
927
|
envTokenReauth,
|
|
921
928
|
createCliConvex
|
|
922
929
|
};
|
|
923
|
-
//# sourceMappingURL=chunk-
|
|
930
|
+
//# sourceMappingURL=chunk-KQNQSVOA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../mcp-runtime/auth/token-store.ts","../../mcp-runtime/paths.ts","../../mcp-runtime/auth/refresh-loop.ts","../../mcp-runtime/auth/auth-provider.ts","../../mcp-runtime/auth/token-exchange.ts","../../mcp-runtime/auth/browser-flow.ts","../../mcp-runtime/aggregate/aggregator.ts","../../mcp-runtime/safety/process-handlers.ts","../../mcp-runtime/transport/pin-interceptor.ts","../../mcp-runtime/elicitation/auth-elicitation.ts","../src/config.ts","../../convex-http-client-auth/client.ts","../../convex-http-client-auth/errors.ts","../src/cli-auth.ts"],"sourcesContent":["/**\n * Persistent token store backed by ~/.tempo/auth.json.\n *\n * - Atomic writes via tmp + rename (safe across concurrent MCP processes).\n * - chmod 600 on every write so other local users can't read the JWT.\n * - fs.watchFile-based change observer so a `tempo-mcp login` in another\n * terminal hot-rotates creds in already-running MCPs (no restart needed).\n *\n * v1 stores the sessionId in the same plaintext JSON. Future enhancement:\n * move sessionId to OS keychain (keytar/Credential Manager/libsecret).\n * See open items in the plan doc.\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { authFilePath, tempoHome } from \"../paths.ts\";\n\nexport interface StoredAuth {\n token: string;\n sessionId: string;\n userId: string;\n email?: string;\n firstName?: string;\n lastName?: string;\n /**\n * Org id remembered from the sign-in handoff, if the auth page sent one.\n * Purely informational — org SCOPE is never read from stored auth; every\n * scoped tool call carries an explicit orgId parameter.\n */\n lastOrgId?: string | null;\n /** Decoded JWT exp (epoch ms). Refresh fires ~60s before this. */\n expiresAt: number;\n /**\n * Non-secret fingerprint (truncated SHA-256) of the TEMPO_AUTH_TOKEN this\n * auth was exchanged from. Absent for browser sign-ins. Lets the CLI make\n * the env token AUTHORITATIVE: cached credentials from a different token\n * (or a browser login) are re-exchanged instead of silently reused —\n * otherwise a shared cloud cache could run as the wrong user.\n */\n accessTokenFingerprint?: string;\n}\n\nexport type AuthListener = (next: StoredAuth | null) => void;\n\n/**\n * Read the auth file. Returns null when the file is missing or unparseable\n * — both legitimate states (first-run / corrupted), and the caller decides\n * how to handle (trigger login, throw, etc).\n */\nexport async function readAuth(): Promise<StoredAuth | null> {\n try {\n const raw = await fsp.readFile(authFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<StoredAuth>;\n if (\n typeof parsed.token === \"string\" &&\n typeof parsed.sessionId === \"string\" &&\n typeof parsed.userId === \"string\" &&\n typeof parsed.expiresAt === \"number\"\n ) {\n return parsed as StoredAuth;\n }\n return null;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n return null;\n }\n}\n\n/**\n * Synchronous reader used by lifecycle hooks that can't await (e.g.\n * `AuthProvider.getToken` in the middle of a Convex call). Returns null\n * on any I/O failure.\n */\nexport function readAuthSync(): StoredAuth | null {\n try {\n const raw = fs.readFileSync(authFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<StoredAuth>;\n if (\n typeof parsed.token === \"string\" &&\n typeof parsed.sessionId === \"string\" &&\n typeof parsed.userId === \"string\" &&\n typeof parsed.expiresAt === \"number\"\n ) {\n return parsed as StoredAuth;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Atomic write: write to a sibling tmp file then rename. Rename is atomic\n * on the same filesystem; a reader either sees the previous file or the\n * new one — never a half-written one.\n */\nexport async function writeAuth(auth: StoredAuth): Promise<void> {\n const dir = tempoHome();\n await fsp.mkdir(dir, { recursive: true, mode: 0o700 });\n const target = authFilePath();\n const tmp = path.join(dir, `auth.json.tmp.${process.pid}.${Date.now()}`);\n const data = JSON.stringify(auth, null, 2);\n await fsp.writeFile(tmp, data, { mode: 0o600 });\n await fsp.rename(tmp, target);\n // rename preserves the source's mode bits on POSIX; explicit chmod is\n // belt-and-braces for filesystems where it doesn't (some network FSes).\n try {\n await fsp.chmod(target, 0o600);\n } catch {\n // Non-fatal — best-effort hardening. Worst case: file is world-readable.\n }\n}\n\n/**\n * Clear the auth file. Used by `tempo-mcp logout` and by the refresh loop\n * when Clerk returns `session_expired`.\n */\nexport async function clearAuth(): Promise<void> {\n try {\n await fsp.unlink(authFilePath());\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n/**\n * Watch the auth file for changes from other processes (e.g. a parallel\n * `tempo-mcp login` writes new creds → all running MCPs pick them up).\n *\n * Returns an unsubscribe function. The listener is called on EVERY mtime\n * change, debounced to one call per 250ms to absorb rename+chmod pairs.\n */\nexport function watchAuth(listener: AuthListener): () => void {\n let timer: NodeJS.Timeout | null = null;\n const file = authFilePath();\n\n const fire = async () => {\n timer = null;\n try {\n const next = await readAuth();\n listener(next);\n } catch {\n listener(null);\n }\n };\n\n const onChange = () => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(fire, 250);\n };\n\n // fs.watchFile polls (interval: 1s) but works on every filesystem,\n // including network mounts where fs.watch is unreliable.\n fs.watchFile(file, { interval: 1000 }, onChange);\n\n return () => {\n fs.unwatchFile(file, onChange);\n if (timer) clearTimeout(timer);\n };\n}\n","/**\n * Filesystem layout for Tempo MCP runtime state.\n *\n * `~/.tempo/auth.json` — JWT, sessionId, userId, lastOrgId, expiresAt\n * `~/.tempo/logs/mcp-*.log` — fatal-error logs (one per binary)\n *\n * Overridable via `TEMPO_MCP_HOME` for tests / parallel CLI processes. The\n * Electron app uses a different file (`~/.tempo-auth.json`) — keeping the\n * paths separate so a CLI login doesn't stomp on an Electron session.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport function tempoHome(): string {\n return process.env.TEMPO_MCP_HOME ?? path.join(os.homedir(), \".tempo\");\n}\n\nexport function authFilePath(): string {\n return path.join(tempoHome(), \"auth.json\");\n}\n\nexport function logsDir(): string {\n return path.join(tempoHome(), \"logs\");\n}\n\nexport function logFilePath(binaryName: string): string {\n const date = new Date().toISOString().slice(0, 10);\n return path.join(logsDir(), `mcp-${binaryName}-${date}.log`);\n}\n","/**\n * Background JWT refresh loop.\n *\n * Refreshes the JWT ~60s before its `expiresAt`. On a hard auth failure\n * (Clerk returns `session_expired` / 401), clears local state and fires\n * `onReauthRequired` so the caller can elicit a fresh sign-in.\n *\n * The implementation is intentionally restartable and stateless across\n * processes — all persistent state lives in `auth.json`, and any number\n * of MCP processes can share the same auth file via fs-watch (only one\n * actually performs the refresh; the others observe and pick up the new\n * token).\n */\n\nimport { readAuth, writeAuth, clearAuth, type StoredAuth } from \"./token-store.ts\";\n\nexport interface RefreshLoopOptions {\n /** Convex site URL (e.g. `https://greedy-jackal-526.convex.site`). No trailing slash. */\n convexSiteUrl: string;\n /** Called when refresh exhausts and a fresh sign-in is required. */\n onReauthRequired: () => void;\n /**\n * Optional headless recovery, tried BEFORE surrendering to\n * `onReauthRequired`: re-acquire credentials without a user (e.g.\n * re-exchange `TEMPO_AUTH_TOKEN` for a fresh session). Return true after\n * writing fresh auth.json; the loop then reads it and carries on.\n */\n attemptReauth?: () => Promise<boolean>;\n /** ms before `expiresAt` to fire the refresh. Default 60_000. */\n refreshLeadMs?: number;\n /** ms between retries on transient failure. Default 5_000. */\n retryDelayMs?: number;\n /** Max retries before giving up + calling onReauthRequired. Default 3. */\n maxRetries?: number;\n}\n\nexport interface RefreshLoopHandle {\n /** Start the loop (schedules the next refresh based on current expiresAt). */\n start: () => void;\n /** Cancel any pending refresh + stop the loop. */\n stop: () => void;\n /** Refresh now, regardless of expiry. Returns the new token or null on failure. */\n refreshNow: () => Promise<string | null>;\n}\n\nexport function createRefreshLoop(opts: RefreshLoopOptions): RefreshLoopHandle {\n const leadMs = opts.refreshLeadMs ?? 60_000;\n const retryDelay = opts.retryDelayMs ?? 5_000;\n const maxRetries = opts.maxRetries ?? 3;\n let timer: NodeJS.Timeout | null = null;\n let stopped = false;\n\n /**\n * Terminal-failure funnel: try headless re-auth first (env-token\n * exchange writes a fresh auth.json), only then bother the caller.\n */\n async function reauthOrGiveUp(): Promise<string | null> {\n if (opts.attemptReauth) {\n try {\n if (await opts.attemptReauth()) {\n const fresh = await readAuth();\n if (fresh) return fresh.token;\n }\n } catch {\n // Fall through — surface as a normal reauth-required.\n }\n }\n opts.onReauthRequired();\n return null;\n }\n\n async function refreshNow(): Promise<string | null> {\n const current = await readAuth();\n if (!current) {\n return reauthOrGiveUp();\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const res = await fetch(`${opts.convexSiteUrl}/auth/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ sessionId: current.sessionId }),\n });\n\n if (res.status === 401) {\n // session_expired — irrecoverable for THIS session; an env token\n // can still mint a new one via attemptReauth.\n await clearAuth();\n return reauthOrGiveUp();\n }\n\n if (!res.ok) {\n // Transient (5xx, network) — retry with backoff.\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n return reauthOrGiveUp();\n }\n\n const body = (await res.json()) as { token?: string };\n if (!body.token) {\n return reauthOrGiveUp();\n }\n\n const next: StoredAuth = {\n ...current,\n token: body.token,\n expiresAt: decodeJwtExp(body.token),\n };\n await writeAuth(next);\n return body.token;\n } catch (err) {\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n // Network failure after retries — don't clear state (transient!),\n // but signal a re-auth flow so the host can surface the issue.\n return reauthOrGiveUp();\n }\n }\n\n return null;\n }\n\n function schedule(auth: StoredAuth) {\n if (timer) clearTimeout(timer);\n if (stopped) return;\n const delay = Math.max(0, auth.expiresAt - Date.now() - leadMs);\n timer = setTimeout(() => {\n void refreshNow().then((newToken) => {\n if (newToken && !stopped) {\n void readAuth().then((a) => {\n if (a) schedule(a);\n });\n }\n });\n }, delay);\n }\n\n function start() {\n stopped = false;\n void readAuth().then((auth) => {\n if (auth) schedule(auth);\n });\n }\n\n function stop() {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n return { start, stop, refreshNow };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/**\n * Decode a JWT's `exp` claim. Returns ms-since-epoch. Falls back to\n * 5-minute-from-now on any decode failure — keeps the refresh loop alive\n * with a sane default until the next successful refresh provides a real exp.\n */\nexport function decodeJwtExp(jwt: string): number {\n try {\n const [, payload] = jwt.split(\".\");\n const decoded = JSON.parse(Buffer.from(payload, \"base64\").toString(\"utf8\")) as { exp?: number };\n if (typeof decoded.exp === \"number\") return decoded.exp * 1000;\n } catch {\n // fall through\n }\n return Date.now() + 5 * 60_000;\n}\n","/**\n * AuthProvider impl that reads from the persistent token store and\n * lazy-refreshes on expiry. Conforms to `@tempo-modules/convex-http-client-auth`'s\n * `AuthProvider` interface so it drops straight into `AuthedConvexClient`.\n *\n * Typical flow:\n * const refresh = createRefreshLoop({ ... });\n * refresh.start();\n * const authProvider = createAuthProvider(refresh);\n * const convex = new AuthedConvexClient({ url, authProvider });\n *\n * The token returned by `getToken()` is whatever's in `auth.json` at the\n * moment of the call. The background refresh loop keeps it fresh; this\n * provider's only job is to read.\n */\n\nimport type {\n AuthProvider,\n AuthToken,\n} from \"@tempo-modules/convex-http-client-auth\";\nimport { readAuth } from \"./token-store.ts\";\nimport type { RefreshLoopHandle } from \"./refresh-loop.ts\";\n\nexport function createAuthProvider(\n refreshLoop: RefreshLoopHandle,\n): AuthProvider {\n return {\n async getToken(): Promise<AuthToken> {\n const auth = await readAuth();\n if (!auth) {\n throw new Error(\n \"Not authenticated. Run `tempo-mcp login` (or sign in via Tempo).\",\n );\n }\n // Refresh proactively if within the lead window. The background loop\n // also handles this — but a stale token surfacing here means the\n // loop hasn't fired yet (cold start, clock skew, etc).\n if (auth.expiresAt - Date.now() < 30_000) {\n const newToken = await refreshLoop.refreshNow();\n if (newToken) {\n const fresh = await readAuth();\n if (fresh) {\n return { jwt: fresh.token, expiresAt: fresh.expiresAt };\n }\n }\n throw new Error(\n \"Tempo session expired. Run `tempo-mcp login` to sign in again.\",\n );\n }\n return { jwt: auth.token, expiresAt: auth.expiresAt };\n },\n };\n}\n","/**\n * Headless sign-in via a Tempo personal access token (TEM-2121).\n *\n * Cloud agent sandboxes (Claude Code cloud, Codex cloud, Grok Bot VMs)\n * have no browser, so the localhost sign-in handoff can never complete\n * there. Instead the environment sets `TEMPO_AUTH_TOKEN=tempo_...` (minted\n * once via `tempo-mcp token create` on a machine that CAN sign in) and\n * this module swaps it at Convex's `/auth/token-exchange` for a fresh\n * Clerk session + JWT, written to the standard auth.json so every\n * downstream consumer (refresh loop, AuthProvider, Convex clients) works\n * unchanged. Each exchange mints a NEW session, so the token outlives\n * Clerk's 7-day session lifetime; revoking it server-side kills the next\n * exchange.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { writeAuth, type StoredAuth } from \"./token-store.ts\";\nimport { decodeJwtExp } from \"./refresh-loop.ts\";\n\nexport const ENV_TOKEN_VAR = \"TEMPO_AUTH_TOKEN\";\n\n/** The `TEMPO_AUTH_TOKEN` value, or null when unset/blank. */\nexport function envAccessToken(): string | null {\n const raw = process.env[ENV_TOKEN_VAR]?.trim();\n return raw ? raw : null;\n}\n\n/**\n * Non-secret identifier for an access token: first 16 hex chars of its\n * SHA-256. Stored in auth.json so cached credentials can be matched to the\n * env token that produced them (16 hex ≈ 64 bits — plenty to distinguish\n * tokens, useless for recovering one).\n */\nexport function accessTokenFingerprint(accessToken: string): string {\n return createHash(\"sha256\").update(accessToken).digest(\"hex\").slice(0, 16);\n}\n\n/**\n * True when `stored` was exchanged from exactly this access token. False\n * for browser sign-ins (no fingerprint) and for a different token — both\n * cases where the env token must win and a fresh exchange is required.\n */\nexport function storedAuthMatchesAccessToken(\n stored: StoredAuth,\n accessToken: string,\n): boolean {\n return stored.accessTokenFingerprint === accessTokenFingerprint(accessToken);\n}\n\nexport class TokenExchangeError extends Error {\n constructor(\n message: string,\n /** `invalid_token` = terminal (revoked/wrong); `transient` = retryable. */\n public readonly kind: \"invalid_token\" | \"transient\",\n ) {\n super(message);\n this.name = \"TokenExchangeError\";\n }\n}\n\nexport interface ExchangeOptions {\n /** Convex site URL, e.g. `https://greedy-jackal-526.convex.site`. */\n convexSiteUrl: string;\n /** The `tempo_...` personal access token. */\n accessToken: string;\n /** Transient-failure retries. Default 2. */\n maxRetries?: number;\n /** ms between retries. Default 2_000. */\n retryDelayMs?: number;\n}\n\n/**\n * Exchange the personal access token for a JWT + session and persist the\n * result to auth.json. Throws `TokenExchangeError` — `invalid_token` means\n * the token is wrong or revoked (re-mint required); `transient` means the\n * backend/Clerk hiccuped and a later retry may succeed.\n */\nexport async function exchangeAccessToken(\n opts: ExchangeOptions,\n): Promise<StoredAuth> {\n const maxRetries = opts.maxRetries ?? 2;\n const retryDelay = opts.retryDelayMs ?? 2_000;\n\n let lastTransient: string = \"exchange failed\";\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n let res: Response;\n try {\n res = await fetch(`${opts.convexSiteUrl}/auth/token-exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token: opts.accessToken }),\n });\n } catch (err) {\n lastTransient = err instanceof Error ? err.message : String(err);\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n break;\n }\n\n if (res.status === 401) {\n throw new TokenExchangeError(\n `${ENV_TOKEN_VAR} was rejected (revoked or invalid). Mint a new token with \\`tempo-mcp token create\\` on a signed-in machine.`,\n \"invalid_token\",\n );\n }\n if (!res.ok) {\n lastTransient = `token exchange returned ${res.status}`;\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n break;\n }\n\n const body = (await res.json()) as {\n token?: string;\n sessionId?: string;\n userId?: string;\n };\n if (!body.token || !body.sessionId || !body.userId) {\n throw new TokenExchangeError(\n \"token exchange returned an incomplete payload\",\n \"transient\",\n );\n }\n\n const stored: StoredAuth = {\n token: body.token,\n sessionId: body.sessionId,\n userId: body.userId,\n email: decodeJwtEmail(body.token),\n expiresAt: decodeJwtExp(body.token),\n accessTokenFingerprint: accessTokenFingerprint(opts.accessToken),\n };\n await writeAuth(stored);\n return stored;\n }\n\n throw new TokenExchangeError(\n `Tempo token exchange failed: ${lastTransient}`,\n \"transient\",\n );\n}\n\n/** The `email` claim from the tempo-local JWT, if present. */\nfunction decodeJwtEmail(jwt: string): string | undefined {\n try {\n const [, payload] = jwt.split(\".\");\n const decoded = JSON.parse(\n Buffer.from(payload, \"base64\").toString(\"utf8\"),\n ) as { email?: string };\n return typeof decoded.email === \"string\" ? decoded.email : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","/**\n * Browser-based OAuth handoff with a localhost HTTP callback listener.\n *\n * Mirrors the Electron app's auth flow (`tempo-client/electron/src/main/adapters/auth.ts`):\n * 1. Bind a localhost HTTP server on `127.0.0.1:0` (kernel-assigned port).\n * 2. Direct the user to `https://auth.tempo.build/sign-in?callbackPort={port}`.\n * 3. After Clerk sign-in, the web-auth page POSTs `{ token, sessionId, userId, ... }`\n * to `127.0.0.1:{port}/tempo/auth` (web-auth Callback.tsx:73-89).\n * 4. We receive the POST, persist to ~/.tempo/auth.json (via writeAuth),\n * respond 200 to unblock the browser, close the listener.\n *\n * Used by:\n * - `tempo-mcp login` (CLI) — `awaitSignIn()` directly, then `console.log` the URL.\n * - MCP elicitation flow — same primitive, the URL goes into the elicitation\n * payload returned to the host.\n */\n\nimport http from \"node:http\";\nimport { writeAuth } from \"./token-store.ts\";\nimport { decodeJwtExp } from \"./refresh-loop.ts\";\n\nexport interface BrowserFlowOptions {\n /** Base URL for the web-auth app. Default `https://auth.tempo.build`. */\n authUrl?: string;\n /**\n * If set, bind to this exact port (e.g. 17249 for parity with the Electron\n * default). Otherwise use a kernel-assigned port. Useful for tests that\n * want a predictable callback URL.\n */\n port?: number;\n /** Timeout in ms for the user to complete sign-in. Default 10 minutes. */\n timeoutMs?: number;\n /** Bind address. Default `127.0.0.1`. */\n bindAddress?: string;\n}\n\nexport interface BrowserFlowHandle {\n /** The fully-qualified URL to send the user to (sign-in page + callback port). */\n signInUrl: string;\n /** The localhost port the listener is bound to. */\n callbackPort: number;\n /**\n * Resolves when the browser POSTs the auth payload (and we've persisted it).\n * Rejects on timeout or listener error.\n */\n done: Promise<void>;\n /** Cancel the listener early (e.g. user aborted). */\n cancel: () => void;\n}\n\n/**\n * Start the browser auth flow. Does NOT open the browser itself — the caller\n * decides whether to call `open` (CLI) or hand the URL to an MCP elicitation\n * payload. Returns immediately with the URL the user should visit.\n */\nexport async function startBrowserAuthFlow(\n opts: BrowserFlowOptions = {},\n): Promise<BrowserFlowHandle> {\n const authUrl = opts.authUrl ?? \"https://auth.tempo.build\";\n const timeoutMs = opts.timeoutMs ?? 10 * 60_000;\n const bindAddress = opts.bindAddress ?? \"127.0.0.1\";\n\n let resolveDone!: () => void;\n let rejectDone!: (err: Error) => void;\n const done = new Promise<void>((resolve, reject) => {\n resolveDone = resolve;\n rejectDone = reject;\n });\n\n const server = http.createServer(async (req, res) => {\n if (\n req.method === \"POST\" &&\n req.url &&\n req.url.startsWith(\"/tempo/auth\")\n ) {\n try {\n const body = await readJson(req);\n if (\n !body ||\n typeof body.token !== \"string\" ||\n typeof body.sessionId !== \"string\" ||\n typeof body.userId !== \"string\"\n ) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"missing fields\" }));\n return;\n }\n\n await writeAuth({\n token: body.token,\n sessionId: body.sessionId,\n userId: body.userId,\n email: typeof body.email === \"string\" ? body.email : undefined,\n firstName: typeof body.firstName === \"string\" ? body.firstName : undefined,\n lastName: typeof body.lastName === \"string\" ? body.lastName : undefined,\n expiresAt: decodeJwtExp(body.token),\n lastOrgId: typeof body.orgId === \"string\" ? body.orgId : null,\n });\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.end(JSON.stringify({ ok: true }));\n // Allow the response to flush before closing.\n setImmediate(() => {\n server.close();\n resolveDone();\n });\n } catch (err) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: String(err) }));\n }\n return;\n }\n\n if (req.method === \"OPTIONS\") {\n // CORS preflight from the auth.tempo.build origin.\n res.writeHead(204, {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n });\n res.end();\n return;\n }\n\n res.writeHead(404);\n res.end();\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(opts.port ?? 0, bindAddress, () => resolve());\n });\n\n const address = server.address();\n if (!address || typeof address === \"string\") {\n server.close();\n throw new Error(\"Failed to bind callback listener\");\n }\n const callbackPort = address.port;\n // `flow=mcp` tells web-auth this sign-in came from the MCP CLI, not the\n // Electron app: web-auth runs its MCP onboarding (persona + org creation)\n // for brand-new accounts and shows terminal-appropriate success copy.\n const signInUrl = `${authUrl}/sign-in?callbackPort=${callbackPort}&flow=mcp`;\n\n const timeoutTimer = setTimeout(() => {\n server.close();\n rejectDone(new Error(`Sign-in timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n // Don't hold the process open just for the timeout\n if (typeof timeoutTimer.unref === \"function\") timeoutTimer.unref();\n\n // Clear the timeout when the user completes auth or cancels. The\n // explicit `.catch()` is required because `.finally()` returns a new\n // promise that re-throws — an unhandled rejection on that chain would\n // otherwise show up as a test/process warning.\n done.finally(() => clearTimeout(timeoutTimer)).catch(() => {});\n\n return {\n signInUrl,\n callbackPort,\n done,\n cancel: () => {\n server.close();\n rejectDone(new Error(\"Sign-in cancelled\"));\n },\n };\n}\n\nfunction readJson(req: http.IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve, reject) => {\n let raw = \"\";\n req.setEncoding(\"utf8\");\n req.on(\"data\", (chunk) => {\n raw += chunk;\n if (raw.length > 1_000_000) {\n // Defensive cap: an attacker on the local network shouldn't be able\n // to spam unbounded data at our auth listener.\n req.destroy();\n reject(new Error(\"payload too large\"));\n }\n });\n req.on(\"end\", () => {\n if (!raw) return resolve(null);\n try {\n resolve(JSON.parse(raw) as Record<string, unknown>);\n } catch (err) {\n reject(err);\n }\n });\n req.on(\"error\", reject);\n });\n}\n","/**\n * In-process MCP aggregator: ONE front server (stdio-facing) that proxies\n * tools and resources from N backend `McpServer` instances over linked\n * in-memory transports.\n *\n * This is what lets `tempo-mcp` present Tempo's whole toolset inventory\n * (issues, docs, comments, agents, scripts, slack, linear, canvas) as a\n * SINGLE MCP server entry in the host's config, while each toolset keeps\n * its existing factory (`create<X>McpServer`) completely unchanged.\n *\n * Request pinning: the factory servers snapshot org/user state per request\n * and hard-fail (`no_active_request`) when no pin is active. External hosts\n * (Claude Code, Codex) expose no turn boundaries, so the aggregator\n * synthesizes one pin per proxied tool call via the backend's\n * `onRequestStart`/`onRequestEnd` — the same contract the Electron host\n * drives per AI turn. The MCP SDK does NOT serialize request handlers, so\n * the aggregator chains every proxied call behind the previous one — at\n * most one pin (and one scope-slot value) is live at a time by\n * construction.\n */\nimport { randomUUID } from \"node:crypto\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type CallToolResult,\n type Resource,\n type Tool,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\n/** Generous in-process ceiling; canvas capture can run for minutes. */\nconst BACKEND_CALL_TIMEOUT_MS = 10 * 60 * 1000;\n\nexport interface AggregateBackend {\n /** Toolset id, e.g. \"issues\", \"docs\", \"canvas\". Used in errors/telemetry. */\n toolset: string;\n server: McpServer;\n /** Per-request pin hooks from the factory instance (see module docs). */\n onRequestStart?: (requestId: string) => void;\n onRequestEnd?: (requestId: string) => void;\n dispose?: () => void;\n}\n\n/**\n * A scope parameter the aggregator ADDS to a proxied tool's input schema\n * (required), extracts from every call, and strips before forwarding to\n * the backend. This is how external connections make org/project scoping\n * EXPLICIT per call while the factory modules keep their ambient\n * `getOrgId()`-style context — the extracted values land in\n * `onScopedCall` before the backend's request pin opens, and the host's\n * getters read them from there.\n */\nexport interface InjectedScopeParam {\n name: string;\n description: string;\n}\n\nexport interface CreateAggregateServerOptions {\n name: string;\n version: string;\n instructions?: string;\n backends: AggregateBackend[];\n /**\n * Registration filter — a tool is exposed only when this returns true.\n * This is where the external tool manifest plugs in (readonly mode,\n * `externalAllowed: false` tools, per-connection `--toolsets`).\n */\n filterTool?: (toolset: string, toolName: string) => boolean;\n /** Scope params to inject into this tool's schema (see the type doc). */\n injectParams?: (toolset: string, toolName: string) => InjectedScopeParam[];\n /**\n * Receives the extracted scope-param values of the CURRENT call, before\n * the backend pin opens. The aggregator serializes tool calls through\n * completion (the SDK does NOT — overlapping requests are possible on\n * one session), so a request-scoped slot written here is race-free.\n */\n onScopedCall?: (values: Record<string, string>) => void;\n}\n\nexport interface AggregateServer {\n /** Connect the front server to its outward transport (stdio, HTTP, ...). */\n connect: (transport: Transport) => Promise<void>;\n /** The low-level front server (exposed for tests). */\n server: Server;\n dispose: () => Promise<void>;\n}\n\ninterface ConnectedBackend extends AggregateBackend {\n client: Client;\n}\n\nexport async function createAggregateServer(\n options: CreateAggregateServerOptions,\n): Promise<AggregateServer> {\n const {\n name,\n version,\n instructions,\n backends,\n filterTool,\n injectParams,\n onScopedCall,\n } = options;\n\n const connected: ConnectedBackend[] = [];\n for (const backend of backends) {\n const [clientTransport, serverTransport] =\n InMemoryTransport.createLinkedPair();\n const client = new Client({\n name: `${name}-aggregator`,\n version,\n });\n await backend.server.connect(serverTransport);\n await client.connect(clientTransport);\n connected.push({ ...backend, client });\n }\n\n // Tool inventories are static after factory registration, so build the\n // routing table once. A name collision across toolsets is a programming\n // error (tools are prefix-namespaced) — fail loudly, don't shadow.\n const toolRoutes = new Map<string, ConnectedBackend>();\n const injectedByTool = new Map<string, InjectedScopeParam[]>();\n const toolsByBackend = new Map<string, Tool[]>();\n for (const backend of connected) {\n if (!backend.client.getServerCapabilities()?.tools) {\n toolsByBackend.set(backend.toolset, []);\n continue;\n }\n const listed = await backend.client.listTools();\n const kept: Tool[] = [];\n for (const tool of listed.tools) {\n if (filterTool && !filterTool(backend.toolset, tool.name)) continue;\n const existing = toolRoutes.get(tool.name);\n if (existing) {\n throw new Error(\n `MCP aggregate tool name collision: \"${tool.name}\" registered by both \"${existing.toolset}\" and \"${backend.toolset}\"`,\n );\n }\n toolRoutes.set(tool.name, backend);\n const injected = injectParams?.(backend.toolset, tool.name) ?? [];\n if (injected.length === 0) {\n kept.push(tool);\n continue;\n }\n injectedByTool.set(tool.name, injected);\n const schema = tool.inputSchema ?? { type: \"object\" as const };\n kept.push({\n ...tool,\n inputSchema: {\n ...schema,\n type: \"object\" as const,\n properties: {\n ...(schema.properties ?? {}),\n ...Object.fromEntries(\n injected.map((param) => [\n param.name,\n { type: \"string\", description: param.description },\n ]),\n ),\n },\n required: [\n ...new Set([\n ...((schema.required as string[] | undefined) ?? []),\n ...injected.map((param) => param.name),\n ]),\n ],\n },\n });\n }\n toolsByBackend.set(backend.toolset, kept);\n }\n\n const server = new Server(\n { name, version },\n {\n capabilities: { tools: {}, resources: {} },\n instructions,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: connected.flatMap(\n (backend) => toolsByBackend.get(backend.toolset) ?? [],\n ),\n }));\n\n // Tail of the in-flight tool-call chain (see the SERIALIZE note below).\n let callChain: Promise<void> = Promise.resolve();\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const toolName = request.params.name;\n const backend = toolRoutes.get(toolName);\n if (!backend) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n error: {\n code: \"unknown_tool\",\n message: `No registered tool named \"${toolName}\"`,\n },\n }),\n },\n ],\n } satisfies CallToolResult;\n }\n // Extract + strip the injected scope params (orgId/projectId) before\n // the backend sees the arguments; missing required scope is a\n // structured tool error naming the discovery tool to call.\n const args: Record<string, unknown> = {\n ...(request.params.arguments ?? {}),\n };\n const injected = injectedByTool.get(toolName) ?? [];\n const scopeValues: Record<string, string> = {};\n for (const param of injected) {\n const value = args[param.name];\n delete args[param.name];\n if (typeof value !== \"string\" || value.length === 0) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n error: {\n code: \"missing_scope_param\",\n message: `\"${toolName}\" requires \"${param.name}\". ${param.description}`,\n },\n }),\n },\n ],\n } satisfies CallToolResult;\n }\n scopeValues[param.name] = value;\n }\n // SERIALIZE through completion. The MCP SDK dispatches each incoming\n // request without awaiting the previous handler, so two tools/call\n // requests CAN overlap — and both the host's scope slot (fed by\n // onScopedCall) and the factories' request-snapshot maps (which fall\n // back to live getters once >1 pin is active) assume one scoped call\n // at a time. Chaining every proxied call behind the previous one makes\n // scope slot + pin + backend execution atomic per request, so a\n // concurrent org-A/org-B pair can never read each other's scope.\n const run = callChain.then(async (): Promise<CallToolResult> => {\n if (injected.length > 0) onScopedCall?.(scopeValues);\n\n const requestId = randomUUID();\n backend.onRequestStart?.(requestId);\n try {\n return (await backend.client.callTool(\n { name: toolName, arguments: args },\n undefined,\n { timeout: BACKEND_CALL_TIMEOUT_MS },\n )) as CallToolResult;\n } finally {\n backend.onRequestEnd?.(requestId);\n }\n });\n callChain = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n });\n\n // Resources: merged list, URI-routed reads. Resource URIs are\n // scheme-namespaced per toolset (e.g. issues://...), so first-listed wins\n // is never exercised in practice; reads route by exact URI, falling back\n // to a live re-list for dynamic resources.\n const listResourcesForBackend = async (\n backend: ConnectedBackend,\n ): Promise<Resource[]> => {\n const caps = backend.client.getServerCapabilities();\n if (!caps?.resources) return [];\n const listed = await backend.client.listResources();\n return listed.resources;\n };\n\n server.setRequestHandler(ListResourcesRequestSchema, async () => {\n const all = await Promise.all(connected.map(listResourcesForBackend));\n return { resources: all.flat() };\n });\n\n server.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n const uri = request.params.uri;\n for (const backend of connected) {\n const resources = await listResourcesForBackend(backend);\n if (resources.some((resource) => resource.uri === uri)) {\n return backend.client.readResource({ uri });\n }\n }\n throw new Error(`Unknown resource: ${uri}`);\n });\n\n return {\n server,\n connect: (transport) => server.connect(transport),\n dispose: async () => {\n for (const backend of connected) {\n await backend.client.close().catch(() => {});\n backend.dispose?.();\n }\n await server.close().catch(() => {});\n },\n };\n}\n","/**\n * Process-level safety net for standalone MCP binaries.\n *\n * MCP-over-stdio is a long-running subprocess that Claude Code spawns and\n * communicates with via JSON-RPC. If our process crashes, the host marks\n * the server \"failed\" for the remainder of the session — there is no\n * client-side reconnect in the MCP protocol. So we install handlers that:\n *\n * - log uncaught exceptions / unhandled rejections to `~/.tempo/logs/`\n * so a user can paste the trace into a bug report\n * - swallow EPIPE on stdout (Claude Code occasionally pauses pipes\n * during ctrl-C; killing the process on EPIPE is a self-inflicted\n * disconnect)\n * - exit cleanly on SIGTERM / SIGINT so the host's \"server stopped\"\n * state is correct\n *\n * Errors that legitimately should crash (out of memory, fatal SDK bugs)\n * still crash — these handlers ONLY guard against the common transient\n * failure modes that today silently take MCP servers offline.\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { logFilePath, logsDir } from \"../paths.ts\";\n\nexport interface ProcessSafetyOptions {\n /** Identifier for log naming. Defaults to `process.argv[1]` basename. */\n binaryName?: string;\n /** Optional shutdown hook called on SIGTERM/SIGINT before exit. */\n onShutdown?: () => Promise<void> | void;\n}\n\nexport function installProcessSafety(opts: ProcessSafetyOptions = {}): () => void {\n const binaryName =\n opts.binaryName ?? path.basename(process.argv[1] ?? \"tempo-mcp\");\n\n const logError = (label: string, err: unknown) => {\n const line = `[${new Date().toISOString()}] ${label}: ${\n err instanceof Error ? err.stack || err.message : String(err)\n }\\n`;\n // Best-effort: never let logging block or crash the process.\n try {\n fs.mkdirSync(logsDir(), { recursive: true, mode: 0o700 });\n fs.appendFileSync(logFilePath(binaryName), line, { mode: 0o600 });\n } catch {\n // last resort: stderr (which the host captures)\n try {\n process.stderr.write(line);\n } catch {\n // give up\n }\n }\n };\n\n const onUncaught = (err: Error) => logError(\"uncaughtException\", err);\n const onUnhandled = (reason: unknown) => logError(\"unhandledRejection\", reason);\n\n process.on(\"uncaughtException\", onUncaught);\n process.on(\"unhandledRejection\", onUnhandled);\n\n // Swallow EPIPE on stdout — Claude Code occasionally pauses our pipe.\n // We can't write to stdout anyway in that case; throwing would be fatal.\n const stdoutError = (err: NodeJS.ErrnoException) => {\n if (err.code !== \"EPIPE\") logError(\"stdout-error\", err);\n };\n process.stdout.on(\"error\", stdoutError);\n\n const onSignal = (signal: NodeJS.Signals) => {\n Promise.resolve(opts.onShutdown?.())\n .catch((err) => logError(`shutdown:${signal}`, err))\n .finally(() => {\n // Use a small delay to let in-flight stdout writes flush before\n // the host kills us.\n setTimeout(() => process.exit(0), 50);\n });\n };\n process.once(\"SIGTERM\", () => onSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", () => onSignal(\"SIGINT\"));\n\n return () => {\n process.off(\"uncaughtException\", onUncaught);\n process.off(\"unhandledRejection\", onUnhandled);\n process.stdout.off(\"error\", stdoutError);\n };\n}\n\n/**\n * Test helper — verify the log file path exists (and optionally has a\n * non-empty body). Returns the resolved path or null.\n */\nexport async function findLatestLogFile(binaryName: string): Promise<string | null> {\n try {\n const entries = await fsp.readdir(logsDir());\n const matching = entries.filter((e) => e.startsWith(`mcp-${binaryName}-`));\n if (matching.length === 0) return null;\n matching.sort();\n return path.join(logsDir(), matching[matching.length - 1]);\n } catch {\n return null;\n }\n}\n","/**\n * Per-tool-call request-id pin synthesis.\n *\n * Tempo's factory-mcp and docs-mcp expose `onRequestStart(id)` /\n * `onRequestEnd(id)` lifecycle hooks that the in-app SessionManager fires at\n * AI request boundaries (so all tool calls in one AI turn see a coherent\n * (orgId, boardId) snapshot). Standalone MCP hosts (Claude Code, Codex CLI)\n * don't expose those boundaries — they just dispatch `tools/call` requests\n * over JSON-RPC.\n *\n * This interceptor synthesizes a pin per tool-call:\n *\n * 1. Wraps `server.server.setRequestHandler` so we observe `tools/call`.\n * 2. Before the underlying handler runs, fire `onRequestStart(uuid)`.\n * 3. In `finally`, fire `onRequestEnd(uuid)`.\n *\n * stdio MCP servers process JSON-RPC requests serially per spec, so the\n * `RequestSnapshotMap.size <= 1` invariant in factory-mcp continues to hold.\n *\n * The interceptor must be installed AFTER all tools are registered (because\n * `McpServer.tool()` registers handlers internally, and we need the final\n * `tools/call` handler to wrap).\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nexport interface PinLifecycle {\n onRequestStart: (requestId: string) => void;\n onRequestEnd: (requestId: string) => void;\n}\n\nexport interface InstallPinInterceptorOptions {\n /** The factory/docs MCP instance providing the lifecycle hooks. */\n lifecycle: PinLifecycle;\n /** Optional fixed prefix on synthesized request ids — useful in logs. */\n idPrefix?: string;\n}\n\n/**\n * Install the per-tool-call pin interceptor. Returns an `uninstall` fn for\n * tests or hot-reload scenarios.\n *\n * Implementation note: the MCP SDK exposes a `server` (the underlying\n * Server instance) under `.server` on `McpServer`. We intercept its\n * `setRequestHandler` by wrapping the `tools/call` handler at registration\n * time. Because `McpServer.tool()` registers `tools/call` itself, we need\n * to install the interceptor *after* that registration — which means\n * doing it just before `server.connect(transport)` in the binary\n * entrypoint.\n */\nexport function installPinInterceptor(\n mcpServer: McpServer,\n opts: InstallPinInterceptorOptions,\n): () => void {\n // McpServer wraps an underlying Server from @modelcontextprotocol/sdk.\n // The `.server` accessor surfaces the underlying Server (typed as `unknown`\n // here to avoid a hard dep on the SDK's internal types).\n const underlying = (mcpServer as unknown as { server: ServerLike }).server;\n if (!underlying || typeof underlying.setRequestHandler !== \"function\") {\n throw new Error(\n \"installPinInterceptor: McpServer has no .server.setRequestHandler — SDK shape changed?\",\n );\n }\n\n // Capture the current `tools/call` handler then re-register a wrapped one.\n // The SDK keeps handlers in a Map keyed by method; setting the same key\n // overrides. We pull the existing handler via the request-handlers map\n // exposed (in SDK 1.27.x) at `_requestHandlers`. If the field name shifts,\n // we fall back to a no-op that calls the wrapped handler directly.\n type Handler = (req: unknown, extra: unknown) => Promise<unknown>;\n const handlers = (underlying as ServerLike & { _requestHandlers?: Map<string, Handler> })\n ._requestHandlers;\n\n const original = handlers?.get(\"tools/call\");\n if (!original) {\n // No tools registered yet, or SDK shape changed. We can still attach\n // a wrapping handler — calls without a registered tool will surface\n // the SDK's normal MethodNotFound error.\n return () => {};\n }\n\n const idPrefix = opts.idPrefix ?? \"mcp\";\n const wrapped: Handler = async (req, extra) => {\n const requestId = `${idPrefix}-${randomUUID()}`;\n try {\n opts.lifecycle.onRequestStart(requestId);\n } catch {\n // Listener errors must not break the request\n }\n try {\n return await original(req, extra);\n } finally {\n try {\n opts.lifecycle.onRequestEnd(requestId);\n } catch {\n // Listener errors must not break the request\n }\n }\n };\n\n handlers!.set(\"tools/call\", wrapped);\n\n return () => {\n if (handlers?.get(\"tools/call\") === wrapped) {\n handlers.set(\"tools/call\", original);\n }\n };\n}\n\n/**\n * Minimal type for the underlying SDK Server we touch — kept narrow so the\n * SDK's full type surface doesn't leak into mcp-runtime consumers.\n */\ninterface ServerLike {\n setRequestHandler: unknown;\n // The actual handler map; existence is verified at runtime.\n _requestHandlers?: Map<\n string,\n (req: unknown, extra: unknown) => Promise<unknown>\n >;\n}\n","/**\n * MCP elicitation-driven sign-in flow.\n *\n * When an MCP starts (or a tool call hits a 401), we want the host\n * (Claude Code, Codex CLI) to prompt the user to sign in. The MCP spec\n * has an `elicitation/create` capability — the server can ask the host\n * to surface a message to the user.\n *\n * Today, real elicitation-prompt support varies by host. As a fallback,\n * we ALSO return the sign-in URL as a tool error so the user can copy/paste\n * it manually. This pairs the slick path (elicitation) with a reliable\n * fallback (URL in the tool result text).\n *\n * Implementation detail: the @modelcontextprotocol/sdk supports the\n * elicitation request via `server.server.createMessage` / equivalent. We\n * wrap that surface in a stable API so the binary entrypoints don't depend\n * on SDK shape.\n */\n\nimport { startBrowserAuthFlow, type BrowserFlowHandle } from \"../auth/browser-flow.ts\";\nimport { readAuth } from \"../auth/token-store.ts\";\n\nexport interface AuthElicitationOptions {\n authUrl?: string;\n timeoutMs?: number;\n}\n\nexport interface AuthElicitationResult {\n /** The user-visible URL — surfaced via elicitation OR in tool error text. */\n signInUrl: string;\n /** Resolves once the user has signed in (or rejects on timeout/cancel). */\n waitForSignIn: () => Promise<void>;\n /** Cancel the listener (e.g. tool call was aborted). */\n cancel: () => void;\n}\n\n/**\n * Returns an `auth_required` elicitation payload AND a localhost listener\n * primed to receive the eventual sign-in. The caller picks how to surface\n * the URL (elicitation, tool error text, etc).\n *\n * Call this from a tool handler when `getToken()` throws \"Not authenticated\"\n * — return the message to the host AND in parallel await `waitForSignIn()`\n * so the tool can retry once auth lands.\n */\nexport async function requestAuthElicitation(\n opts: AuthElicitationOptions = {},\n): Promise<AuthElicitationResult> {\n const handle: BrowserFlowHandle = await startBrowserAuthFlow({\n authUrl: opts.authUrl,\n timeoutMs: opts.timeoutMs ?? 5 * 60_000,\n });\n\n return {\n signInUrl: handle.signInUrl,\n waitForSignIn: () => handle.done,\n cancel: () => handle.cancel(),\n };\n}\n\n/**\n * One-shot helper for binary entrypoints: returns immediately if auth\n * already exists, otherwise blocks until the user completes sign-in via\n * the browser handoff. Used by `tempo-mcp login` and by an MCP binary's\n * startup probe.\n */\nexport async function ensureAuth(opts: AuthElicitationOptions = {}): Promise<{\n hadExistingAuth: boolean;\n signInUrl?: string;\n}> {\n const existing = await readAuth();\n if (existing && existing.expiresAt - Date.now() > 60_000) {\n return { hadExistingAuth: true };\n }\n const eli = await requestAuthElicitation(opts);\n await eli.waitForSignIn();\n return { hadExistingAuth: false, signInUrl: eli.signInUrl };\n}\n","/**\n * Environment + workspace resolution for `tempo-mcp serve`.\n *\n * Convex endpoints default to production so `npx @tempo-ai/mcp` works with\n * zero configuration; TEMPO_CONVEX_URL / TEMPO_CONVEX_SITE_URL override\n * for previews and staging.\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport const DEFAULT_CONVEX_URL = \"https://greedy-jackal-526.convex.cloud\";\n\nexport function resolveConvexUrl(): string {\n return process.env.TEMPO_CONVEX_URL ?? DEFAULT_CONVEX_URL;\n}\n\nexport function resolveConvexSiteUrl(convexUrl: string): string {\n const explicit = process.env.TEMPO_CONVEX_SITE_URL;\n if (explicit) return explicit;\n return convexUrl.replace(/\\.convex\\.cloud$/, \".convex.site\");\n}\n\nexport const ALL_TOOLSETS = [\n \"issues\",\n \"docs\",\n \"comments\",\n \"agents\",\n \"scripts\",\n \"slack\",\n \"linear\",\n \"canvas\",\n] as const;\n\nexport type ToolsetId = (typeof ALL_TOOLSETS)[number];\n\nexport interface ServeOptions {\n toolsets: ToolsetId[];\n readonly: boolean;\n}\n\n/**\n * NOTE: org/project scope is deliberately NOT configurable here — the AI\n * passes an explicit `orgId`/`projectId` on every scoped tool call\n * (discovered via tempo_list_orgs / tempo_list_projects). The only flags\n * are capability-shaping: which toolsets, and readonly.\n */\nexport function parseServeArgs(args: string[]): ServeOptions | { error: string } {\n let toolsets: ToolsetId[] = [...ALL_TOOLSETS];\n let readonly = process.env.TEMPO_MCP_READONLY === \"1\";\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"--readonly\") {\n readonly = true;\n } else if (arg === \"--toolsets\") {\n const value = args[++i];\n if (!value) return { error: \"--toolsets requires a comma-separated list\" };\n const requested = value.split(\",\").map((t) => t.trim()).filter(Boolean);\n const unknown = requested.filter(\n (t) => !ALL_TOOLSETS.includes(t as ToolsetId),\n );\n if (unknown.length) {\n return {\n error: `Unknown toolset(s): ${unknown.join(\", \")}. Valid: ${ALL_TOOLSETS.join(\", \")}`,\n };\n }\n toolsets = requested as ToolsetId[];\n } else {\n return { error: `Unknown flag: ${arg}` };\n }\n }\n return { toolsets, readonly };\n}\n\nexport interface DiscoveredWorkspace {\n workspaceRoot: string;\n canvasesDir: string;\n}\n\n/**\n * Walk upward from `startDir` looking for `tempo/tempo.config.json` — the\n * marker of a Tempo-canvas-enabled repo. The canvas toolset registers only\n * when this resolves; in any other cwd the canvas tools simply don't exist.\n */\nexport function discoverWorkspace(\n startDir = process.cwd(),\n): DiscoveredWorkspace | null {\n let dir = path.resolve(startDir);\n for (;;) {\n const configPath = path.join(dir, \"tempo\", \"tempo.config.json\");\n if (fs.existsSync(configPath)) {\n let canvasesRel = \"./designs\";\n try {\n const parsed = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as {\n paths?: { canvases?: string };\n };\n if (parsed.paths?.canvases) canvasesRel = parsed.paths.canvases;\n } catch {\n // Unparseable config — fall back to the conventional location.\n }\n return {\n workspaceRoot: dir,\n canvasesDir: path.resolve(dir, \"tempo\", canvasesRel),\n };\n }\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n","import { ConvexHttpClient } from \"convex/browser\";\nimport type { FunctionReference, FunctionArgs, FunctionReturnType } from \"convex/server\";\nimport { mapConvexError, McpToolError, type McpError } from \"./errors.ts\";\n\nexport interface AuthToken {\n jwt: string;\n expiresAt: number;\n}\n\nexport interface AuthProvider {\n getToken(): Promise<AuthToken>;\n}\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 200;\n\nexport class AuthedConvexClient {\n private client: ConvexHttpClient;\n private authProvider: AuthProvider;\n private cachedToken: AuthToken | null = null;\n\n constructor(opts: { url: string; authProvider: AuthProvider }) {\n this.client = new ConvexHttpClient(opts.url);\n this.authProvider = opts.authProvider;\n }\n\n async query<F extends FunctionReference<\"query\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.query(fn, args));\n }\n\n async mutation<F extends FunctionReference<\"mutation\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.mutation(fn, args));\n }\n\n async action<F extends FunctionReference<\"action\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.action(fn, args));\n }\n\n private async ensureAuth(): Promise<void> {\n const now = Date.now();\n if (this.cachedToken && this.cachedToken.expiresAt - now > 30_000) {\n this.client.setAuth(this.cachedToken.jwt);\n return;\n }\n const token = await this.authProvider.getToken();\n this.cachedToken = token;\n this.client.setAuth(token.jwt);\n }\n\n private async callWithAuth<T>(fn: () => Promise<T>): Promise<T> {\n await this.ensureAuth();\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n return await fn();\n } catch (err: unknown) {\n const mapped = mapConvexError(err);\n\n if (mapped.code === \"auth_required\" && attempt === 0) {\n this.cachedToken = null;\n await this.ensureAuth();\n continue;\n }\n\n if (mapped.code === \"internal\" && attempt < MAX_RETRIES - 1) {\n await sleep(BASE_DELAY_MS * 2 ** attempt);\n continue;\n }\n\n throw new McpToolError(mapped);\n }\n }\n\n throw new McpToolError({\n code: \"internal\",\n message: \"Max retries exceeded\",\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","export interface McpError {\n code:\n | \"auth_required\"\n | \"forbidden\"\n | \"not_found\"\n | \"invalid_args\"\n | \"batch_too_large\"\n | \"confirmation_required\"\n | \"duplicate\"\n | \"rate_limited\"\n | \"branch_not_pushed\"\n | \"internal\";\n message: string;\n details?: Record<string, unknown>;\n}\n\nexport class McpToolError extends Error {\n public readonly error: McpError;\n constructor(error: McpError) {\n super(error.message);\n this.name = \"McpToolError\";\n this.error = error;\n }\n}\n\nexport function mapConvexError(err: unknown): McpError {\n const message =\n err instanceof Error ? err.message : String(err);\n\n if (message.includes(\"Not authenticated\") || message.includes(\"Unauthenticated\")) {\n return { code: \"auth_required\", message: \"Authentication required. Please sign in.\" };\n }\n\n if (message.includes(\"not a member\") || message.includes(\"Unauthorized\") || message.includes(\"forbidden\")) {\n return { code: \"forbidden\", message: \"You are not a member of this organization.\" };\n }\n\n if (message.includes(\"not found\") || message.includes(\"Could not find\")) {\n return { code: \"not_found\", message };\n }\n\n if (message.includes(\"rate limit\") || message.includes(\"Too many requests\")) {\n return { code: \"rate_limited\", message: \"Rate limited. Please retry after a moment.\" };\n }\n\n if (message.includes(\"Invalid argument\") || message.includes(\"Validator error\")) {\n return { code: \"invalid_args\", message };\n }\n\n return { code: \"internal\", message };\n}\n","/**\n * Shared auth entry for every CLI code path (TEM-2121).\n *\n * Two ways in, ONE stored shape:\n * - `TEMPO_AUTH_TOKEN` set (headless cloud sandboxes) → exchange it at\n * `/auth/token-exchange` for a fresh Clerk session + JWT.\n * - Otherwise → the browser sign-in handoff (`ensureAuth`).\n *\n * Both write the standard auth.json, so the refresh loop, AuthProvider,\n * and every Convex client work identically afterwards.\n */\nimport {\n AuthedConvexClient,\n type AuthProvider,\n} from \"@tempo-modules/convex-http-client-auth\";\nimport {\n createAuthProvider,\n createRefreshLoop,\n ensureAuth,\n envAccessToken,\n exchangeAccessToken,\n readAuth,\n storedAuthMatchesAccessToken,\n type RefreshLoopHandle,\n} from \"@tempo-modules/mcp-runtime\";\nimport { resolveConvexSiteUrl, resolveConvexUrl } from \"./config.ts\";\n\n/**\n * Ensure usable credentials exist, preferring the env token when set.\n *\n * With `TEMPO_AUTH_TOKEN` present the env token is AUTHORITATIVE: cached\n * credentials are only reused when their fingerprint proves they came from\n * this exact token. A cache left by a browser login or a DIFFERENT token\n * (shared cloud home dirs) is re-exchanged, never silently run as the\n * other user. And we never fall back to an interactive browser flow that\n * can't complete in a sandbox.\n */\nexport async function ensureCliAuth(): Promise<void> {\n const accessToken = envAccessToken();\n if (!accessToken) {\n await ensureAuth();\n return;\n }\n const existing = await readAuth();\n if (\n existing &&\n storedAuthMatchesAccessToken(existing, accessToken) &&\n existing.expiresAt - Date.now() > 60_000\n ) {\n return;\n }\n await exchangeAccessToken({\n convexSiteUrl: resolveConvexSiteUrl(resolveConvexUrl()),\n accessToken,\n });\n}\n\n/**\n * The refresh loop's headless recovery hook: re-exchange the env token\n * when the current session dies mid-run. No env token → no recovery\n * (returns false and the loop surfaces reauth-required as before).\n */\nexport function envTokenReauth(convexSiteUrl: string): () => Promise<boolean> {\n return async () => {\n const accessToken = envAccessToken();\n if (!accessToken) return false;\n await exchangeAccessToken({ convexSiteUrl, accessToken });\n return true;\n };\n}\n\nexport interface CliConvexHandle {\n convex: AuthedConvexClient;\n authProvider: AuthProvider;\n dispose: () => void;\n}\n\n/**\n * Auth-wired Convex client for short-lived subcommands (`token …`). The\n * caller must `dispose()` so the refresh timer doesn't hold the process\n * open.\n */\nexport function createCliConvex(): CliConvexHandle {\n const convexUrl = resolveConvexUrl();\n const convexSiteUrl = resolveConvexSiteUrl(convexUrl);\n const refreshLoop: RefreshLoopHandle = createRefreshLoop({\n convexSiteUrl,\n attemptReauth: envTokenReauth(convexSiteUrl),\n onReauthRequired: () => {\n console.error(\n \"[tempo-mcp] Tempo session expired. Run `tempo-mcp login` to sign in again.\",\n );\n },\n });\n refreshLoop.start();\n const authProvider = createAuthProvider(refreshLoop);\n const convex = new AuthedConvexClient({ url: convexUrl, authProvider });\n return { convex, authProvider, dispose: () => refreshLoop.stop() };\n}\n"],"mappings":"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","names":["path","path","sleep","fs","fsp","path","path","fs","randomUUID","fs","path","sleep"]}
|
|
1
|
+
{"version":3,"sources":["../../mcp-runtime/auth/token-store.ts","../../mcp-runtime/paths.ts","../../mcp-runtime/auth/refresh-loop.ts","../../mcp-runtime/auth/auth-provider.ts","../../mcp-runtime/auth/token-exchange.ts","../../mcp-runtime/auth/browser-flow.ts","../../mcp-runtime/aggregate/aggregator.ts","../../mcp-runtime/safety/process-handlers.ts","../../mcp-runtime/transport/pin-interceptor.ts","../../mcp-runtime/elicitation/auth-elicitation.ts","../src/config.ts","../../convex-http-client-auth/client.ts","../../convex-http-client-auth/errors.ts","../src/cli-auth.ts"],"sourcesContent":["/**\n * Persistent token store backed by ~/.tempo/auth.json.\n *\n * - Atomic writes via tmp + rename (safe across concurrent MCP processes).\n * - chmod 600 on every write so other local users can't read the JWT.\n * - fs.watchFile-based change observer so a `tempo-mcp login` in another\n * terminal hot-rotates creds in already-running MCPs (no restart needed).\n *\n * v1 stores the sessionId in the same plaintext JSON. Future enhancement:\n * move sessionId to OS keychain (keytar/Credential Manager/libsecret).\n * See open items in the plan doc.\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { authFilePath, tempoHome } from \"../paths.ts\";\n\nexport interface StoredAuth {\n token: string;\n sessionId: string;\n userId: string;\n email?: string;\n firstName?: string;\n lastName?: string;\n /**\n * Org id remembered from the sign-in handoff, if the auth page sent one.\n * Purely informational — org SCOPE is never read from stored auth; every\n * scoped tool call carries an explicit orgId parameter.\n */\n lastOrgId?: string | null;\n /** Decoded JWT exp (epoch ms). Refresh fires ~60s before this. */\n expiresAt: number;\n /**\n * Non-secret fingerprint (truncated SHA-256) of the TEMPO_AUTH_TOKEN this\n * auth was exchanged from. Absent for browser sign-ins. Lets the CLI make\n * the env token AUTHORITATIVE: cached credentials from a different token\n * (or a browser login) are re-exchanged instead of silently reused —\n * otherwise a shared cloud cache could run as the wrong user.\n */\n accessTokenFingerprint?: string;\n}\n\nexport type AuthListener = (next: StoredAuth | null) => void;\n\n/**\n * Read the auth file. Returns null when the file is missing or unparseable\n * — both legitimate states (first-run / corrupted), and the caller decides\n * how to handle (trigger login, throw, etc).\n */\nexport async function readAuth(): Promise<StoredAuth | null> {\n try {\n const raw = await fsp.readFile(authFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<StoredAuth>;\n if (\n typeof parsed.token === \"string\" &&\n typeof parsed.sessionId === \"string\" &&\n typeof parsed.userId === \"string\" &&\n typeof parsed.expiresAt === \"number\"\n ) {\n return parsed as StoredAuth;\n }\n return null;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n return null;\n }\n}\n\n/**\n * Synchronous reader used by lifecycle hooks that can't await (e.g.\n * `AuthProvider.getToken` in the middle of a Convex call). Returns null\n * on any I/O failure.\n */\nexport function readAuthSync(): StoredAuth | null {\n try {\n const raw = fs.readFileSync(authFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<StoredAuth>;\n if (\n typeof parsed.token === \"string\" &&\n typeof parsed.sessionId === \"string\" &&\n typeof parsed.userId === \"string\" &&\n typeof parsed.expiresAt === \"number\"\n ) {\n return parsed as StoredAuth;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Atomic write: write to a sibling tmp file then rename. Rename is atomic\n * on the same filesystem; a reader either sees the previous file or the\n * new one — never a half-written one.\n */\nexport async function writeAuth(auth: StoredAuth): Promise<void> {\n const dir = tempoHome();\n await fsp.mkdir(dir, { recursive: true, mode: 0o700 });\n const target = authFilePath();\n const tmp = path.join(dir, `auth.json.tmp.${process.pid}.${Date.now()}`);\n const data = JSON.stringify(auth, null, 2);\n await fsp.writeFile(tmp, data, { mode: 0o600 });\n await fsp.rename(tmp, target);\n // rename preserves the source's mode bits on POSIX; explicit chmod is\n // belt-and-braces for filesystems where it doesn't (some network FSes).\n try {\n await fsp.chmod(target, 0o600);\n } catch {\n // Non-fatal — best-effort hardening. Worst case: file is world-readable.\n }\n}\n\n/**\n * Clear the auth file. Used by `tempo-mcp logout` and by the refresh loop\n * when Clerk returns `session_expired`.\n */\nexport async function clearAuth(): Promise<void> {\n try {\n await fsp.unlink(authFilePath());\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n/**\n * Watch the auth file for changes from other processes (e.g. a parallel\n * `tempo-mcp login` writes new creds → all running MCPs pick them up).\n *\n * Returns an unsubscribe function. The listener is called on EVERY mtime\n * change, debounced to one call per 250ms to absorb rename+chmod pairs.\n */\nexport function watchAuth(listener: AuthListener): () => void {\n let timer: NodeJS.Timeout | null = null;\n const file = authFilePath();\n\n const fire = async () => {\n timer = null;\n try {\n const next = await readAuth();\n listener(next);\n } catch {\n listener(null);\n }\n };\n\n const onChange = () => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(fire, 250);\n };\n\n // fs.watchFile polls (interval: 1s) but works on every filesystem,\n // including network mounts where fs.watch is unreliable.\n fs.watchFile(file, { interval: 1000 }, onChange);\n\n return () => {\n fs.unwatchFile(file, onChange);\n if (timer) clearTimeout(timer);\n };\n}\n","/**\n * Filesystem layout for Tempo MCP runtime state.\n *\n * `~/.tempo/auth.json` — JWT, sessionId, userId, lastOrgId, expiresAt\n * `~/.tempo/logs/mcp-*.log` — fatal-error logs (one per binary)\n *\n * Overridable via `TEMPO_MCP_HOME` for tests / parallel CLI processes. The\n * Electron app uses a different file (`~/.tempo-auth.json`) — keeping the\n * paths separate so a CLI login doesn't stomp on an Electron session.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport function tempoHome(): string {\n return process.env.TEMPO_MCP_HOME ?? path.join(os.homedir(), \".tempo\");\n}\n\nexport function authFilePath(): string {\n return path.join(tempoHome(), \"auth.json\");\n}\n\nexport function logsDir(): string {\n return path.join(tempoHome(), \"logs\");\n}\n\nexport function logFilePath(binaryName: string): string {\n const date = new Date().toISOString().slice(0, 10);\n return path.join(logsDir(), `mcp-${binaryName}-${date}.log`);\n}\n","/**\n * Background JWT refresh loop.\n *\n * Refreshes the JWT ~60s before its `expiresAt`. On a hard auth failure\n * (Clerk returns `session_expired` / 401), clears local state and fires\n * `onReauthRequired` so the caller can elicit a fresh sign-in.\n *\n * The implementation is intentionally restartable and stateless across\n * processes — all persistent state lives in `auth.json`, and any number\n * of MCP processes can share the same auth file via fs-watch (only one\n * actually performs the refresh; the others observe and pick up the new\n * token).\n */\n\nimport { readAuth, writeAuth, clearAuth, type StoredAuth } from \"./token-store.ts\";\n\nexport interface RefreshLoopOptions {\n /** Convex site URL (e.g. `https://greedy-jackal-526.convex.site`). No trailing slash. */\n convexSiteUrl: string;\n /** Called when refresh exhausts and a fresh sign-in is required. */\n onReauthRequired: () => void;\n /**\n * Optional headless recovery, tried BEFORE surrendering to\n * `onReauthRequired`: re-acquire credentials without a user (e.g.\n * re-exchange `TEMPO_AUTH_TOKEN` for a fresh session). Return true after\n * writing fresh auth.json; the loop then reads it and carries on.\n */\n attemptReauth?: () => Promise<boolean>;\n /** ms before `expiresAt` to fire the refresh. Default 60_000. */\n refreshLeadMs?: number;\n /** ms between retries on transient failure. Default 5_000. */\n retryDelayMs?: number;\n /** Max retries before giving up + calling onReauthRequired. Default 3. */\n maxRetries?: number;\n}\n\nexport interface RefreshLoopHandle {\n /** Start the loop (schedules the next refresh based on current expiresAt). */\n start: () => void;\n /** Cancel any pending refresh + stop the loop. */\n stop: () => void;\n /** Refresh now, regardless of expiry. Returns the new token or null on failure. */\n refreshNow: () => Promise<string | null>;\n}\n\nexport function createRefreshLoop(opts: RefreshLoopOptions): RefreshLoopHandle {\n const leadMs = opts.refreshLeadMs ?? 60_000;\n const retryDelay = opts.retryDelayMs ?? 5_000;\n const maxRetries = opts.maxRetries ?? 3;\n let timer: NodeJS.Timeout | null = null;\n let stopped = false;\n\n /**\n * Terminal-failure funnel: try headless re-auth first (env-token\n * exchange writes a fresh auth.json), only then bother the caller.\n */\n async function reauthOrGiveUp(): Promise<string | null> {\n if (opts.attemptReauth) {\n try {\n if (await opts.attemptReauth()) {\n const fresh = await readAuth();\n if (fresh) return fresh.token;\n }\n } catch {\n // Fall through — surface as a normal reauth-required.\n }\n }\n opts.onReauthRequired();\n return null;\n }\n\n async function refreshNow(): Promise<string | null> {\n const current = await readAuth();\n if (!current) {\n return reauthOrGiveUp();\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const res = await fetch(`${opts.convexSiteUrl}/auth/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ sessionId: current.sessionId }),\n });\n\n if (res.status === 401) {\n // session_expired — irrecoverable for THIS session; an env token\n // can still mint a new one via attemptReauth.\n await clearAuth();\n return reauthOrGiveUp();\n }\n\n if (!res.ok) {\n // Transient (5xx, network) — retry with backoff.\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n return reauthOrGiveUp();\n }\n\n const body = (await res.json()) as { token?: string };\n if (!body.token) {\n return reauthOrGiveUp();\n }\n\n const next: StoredAuth = {\n ...current,\n token: body.token,\n expiresAt: decodeJwtExp(body.token),\n };\n await writeAuth(next);\n return body.token;\n } catch (err) {\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n // Network failure after retries — don't clear state (transient!),\n // but signal a re-auth flow so the host can surface the issue.\n return reauthOrGiveUp();\n }\n }\n\n return null;\n }\n\n function schedule(auth: StoredAuth) {\n if (timer) clearTimeout(timer);\n if (stopped) return;\n const delay = Math.max(0, auth.expiresAt - Date.now() - leadMs);\n timer = setTimeout(() => {\n void refreshNow().then((newToken) => {\n if (newToken && !stopped) {\n void readAuth().then((a) => {\n if (a) schedule(a);\n });\n }\n });\n }, delay);\n }\n\n function start() {\n stopped = false;\n void readAuth().then((auth) => {\n if (auth) schedule(auth);\n });\n }\n\n function stop() {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n return { start, stop, refreshNow };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/**\n * Decode a JWT's `exp` claim. Returns ms-since-epoch. Falls back to\n * 5-minute-from-now on any decode failure — keeps the refresh loop alive\n * with a sane default until the next successful refresh provides a real exp.\n */\nexport function decodeJwtExp(jwt: string): number {\n try {\n const [, payload] = jwt.split(\".\");\n const decoded = JSON.parse(Buffer.from(payload, \"base64\").toString(\"utf8\")) as { exp?: number };\n if (typeof decoded.exp === \"number\") return decoded.exp * 1000;\n } catch {\n // fall through\n }\n return Date.now() + 5 * 60_000;\n}\n","/**\n * AuthProvider impl that reads from the persistent token store and\n * lazy-refreshes on expiry. Conforms to `@tempo-modules/convex-http-client-auth`'s\n * `AuthProvider` interface so it drops straight into `AuthedConvexClient`.\n *\n * Typical flow:\n * const refresh = createRefreshLoop({ ... });\n * refresh.start();\n * const authProvider = createAuthProvider(refresh);\n * const convex = new AuthedConvexClient({ url, authProvider });\n *\n * The token returned by `getToken()` is whatever's in `auth.json` at the\n * moment of the call. The background refresh loop keeps it fresh; this\n * provider's only job is to read.\n */\n\nimport type {\n AuthProvider,\n AuthToken,\n} from \"@tempo-modules/convex-http-client-auth\";\nimport { readAuth } from \"./token-store.ts\";\nimport type { RefreshLoopHandle } from \"./refresh-loop.ts\";\n\nexport function createAuthProvider(\n refreshLoop: RefreshLoopHandle,\n): AuthProvider {\n return {\n async getToken(): Promise<AuthToken> {\n const auth = await readAuth();\n if (!auth) {\n throw new Error(\n \"Not authenticated. Run `tempo-mcp login` (or sign in via Tempo).\",\n );\n }\n // Refresh proactively if within the lead window. The background loop\n // also handles this — but a stale token surfacing here means the\n // loop hasn't fired yet (cold start, clock skew, etc).\n if (auth.expiresAt - Date.now() < 30_000) {\n const newToken = await refreshLoop.refreshNow();\n if (newToken) {\n const fresh = await readAuth();\n if (fresh) {\n return { jwt: fresh.token, expiresAt: fresh.expiresAt };\n }\n }\n throw new Error(\n \"Tempo session expired. Run `tempo-mcp login` to sign in again.\",\n );\n }\n return { jwt: auth.token, expiresAt: auth.expiresAt };\n },\n };\n}\n","/**\n * Headless sign-in via a Tempo personal access token (TEM-2121).\n *\n * Cloud agent sandboxes (Claude Code cloud, Codex cloud, Grok Bot VMs)\n * have no browser, so the localhost sign-in handoff can never complete\n * there. Instead the environment sets `TEMPO_AUTH_TOKEN=tempo_...` (minted\n * once via `tempo-mcp token create` on a machine that CAN sign in) and\n * this module swaps it at Convex's `/auth/token-exchange` for a fresh\n * Clerk session + JWT, written to the standard auth.json so every\n * downstream consumer (refresh loop, AuthProvider, Convex clients) works\n * unchanged. Each exchange mints a NEW session, so the token outlives\n * Clerk's 7-day session lifetime; revoking it server-side kills the next\n * exchange.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { writeAuth, type StoredAuth } from \"./token-store.ts\";\nimport { decodeJwtExp } from \"./refresh-loop.ts\";\n\nexport const ENV_TOKEN_VAR = \"TEMPO_AUTH_TOKEN\";\n\n/** The `TEMPO_AUTH_TOKEN` value, or null when unset/blank. */\nexport function envAccessToken(): string | null {\n const raw = process.env[ENV_TOKEN_VAR]?.trim();\n return raw ? raw : null;\n}\n\n/**\n * Non-secret identifier for an access token: first 16 hex chars of its\n * SHA-256. Stored in auth.json so cached credentials can be matched to the\n * env token that produced them (16 hex ≈ 64 bits — plenty to distinguish\n * tokens, useless for recovering one).\n */\nexport function accessTokenFingerprint(accessToken: string): string {\n return createHash(\"sha256\").update(accessToken).digest(\"hex\").slice(0, 16);\n}\n\n/**\n * True when `stored` was exchanged from exactly this access token. False\n * for browser sign-ins (no fingerprint) and for a different token — both\n * cases where the env token must win and a fresh exchange is required.\n */\nexport function storedAuthMatchesAccessToken(\n stored: StoredAuth,\n accessToken: string,\n): boolean {\n return stored.accessTokenFingerprint === accessTokenFingerprint(accessToken);\n}\n\nexport class TokenExchangeError extends Error {\n constructor(\n message: string,\n /** `invalid_token` = terminal (revoked/wrong); `transient` = retryable. */\n public readonly kind: \"invalid_token\" | \"transient\",\n ) {\n super(message);\n this.name = \"TokenExchangeError\";\n }\n}\n\nexport interface ExchangeOptions {\n /** Convex site URL, e.g. `https://greedy-jackal-526.convex.site`. */\n convexSiteUrl: string;\n /** The `tempo_...` personal access token. */\n accessToken: string;\n /** Transient-failure retries. Default 2. */\n maxRetries?: number;\n /** ms between retries. Default 2_000. */\n retryDelayMs?: number;\n}\n\n/**\n * Exchange the personal access token for a JWT + session and persist the\n * result to auth.json. Throws `TokenExchangeError` — `invalid_token` means\n * the token is wrong or revoked (re-mint required); `transient` means the\n * backend/Clerk hiccuped and a later retry may succeed.\n */\nexport async function exchangeAccessToken(\n opts: ExchangeOptions,\n): Promise<StoredAuth> {\n const maxRetries = opts.maxRetries ?? 2;\n const retryDelay = opts.retryDelayMs ?? 2_000;\n\n let lastTransient: string = \"exchange failed\";\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n let res: Response;\n try {\n res = await fetch(`${opts.convexSiteUrl}/auth/token-exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token: opts.accessToken }),\n });\n } catch (err) {\n lastTransient = err instanceof Error ? err.message : String(err);\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n break;\n }\n\n if (res.status === 401) {\n throw new TokenExchangeError(\n `${ENV_TOKEN_VAR} was rejected (revoked or invalid). Mint a new token with \\`tempo-mcp token create\\` on a signed-in machine.`,\n \"invalid_token\",\n );\n }\n if (!res.ok) {\n lastTransient = `token exchange returned ${res.status}`;\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n break;\n }\n\n const body = (await res.json()) as {\n token?: string;\n sessionId?: string;\n userId?: string;\n };\n if (!body.token || !body.sessionId || !body.userId) {\n throw new TokenExchangeError(\n \"token exchange returned an incomplete payload\",\n \"transient\",\n );\n }\n\n const stored: StoredAuth = {\n token: body.token,\n sessionId: body.sessionId,\n userId: body.userId,\n email: decodeJwtEmail(body.token),\n expiresAt: decodeJwtExp(body.token),\n accessTokenFingerprint: accessTokenFingerprint(opts.accessToken),\n };\n await writeAuth(stored);\n return stored;\n }\n\n throw new TokenExchangeError(\n `Tempo token exchange failed: ${lastTransient}`,\n \"transient\",\n );\n}\n\n/** The `email` claim from the tempo-local JWT, if present. */\nfunction decodeJwtEmail(jwt: string): string | undefined {\n try {\n const [, payload] = jwt.split(\".\");\n const decoded = JSON.parse(\n Buffer.from(payload, \"base64\").toString(\"utf8\"),\n ) as { email?: string };\n return typeof decoded.email === \"string\" ? decoded.email : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","/**\n * Browser-based OAuth handoff with a localhost HTTP callback listener.\n *\n * Mirrors the Electron app's auth flow (`tempo-client/electron/src/main/adapters/auth.ts`):\n * 1. Bind a localhost HTTP server on `127.0.0.1:0` (kernel-assigned port).\n * 2. Direct the user to `https://auth.tempo.build/sign-in?callbackPort={port}`.\n * 3. After Clerk sign-in, the web-auth page POSTs `{ token, sessionId, userId, ... }`\n * to `127.0.0.1:{port}/tempo/auth` (web-auth Callback.tsx:73-89).\n * 4. We receive the POST, persist to ~/.tempo/auth.json (via writeAuth),\n * respond 200 to unblock the browser, close the listener.\n *\n * Used by:\n * - `tempo-mcp login` (CLI) — `awaitSignIn()` directly, then `console.log` the URL.\n * - MCP elicitation flow — same primitive, the URL goes into the elicitation\n * payload returned to the host.\n */\n\nimport http from \"node:http\";\nimport { writeAuth } from \"./token-store.ts\";\nimport { decodeJwtExp } from \"./refresh-loop.ts\";\n\nexport interface BrowserFlowOptions {\n /** Base URL for the web-auth app. Default `https://auth.tempo.build`. */\n authUrl?: string;\n /**\n * If set, bind to this exact port (e.g. 17249 for parity with the Electron\n * default). Otherwise use a kernel-assigned port. Useful for tests that\n * want a predictable callback URL.\n */\n port?: number;\n /** Timeout in ms for the user to complete sign-in. Default 10 minutes. */\n timeoutMs?: number;\n /** Bind address. Default `127.0.0.1`. */\n bindAddress?: string;\n}\n\nexport interface BrowserFlowHandle {\n /** The fully-qualified URL to send the user to (sign-in page + callback port). */\n signInUrl: string;\n /** The localhost port the listener is bound to. */\n callbackPort: number;\n /**\n * Resolves when the browser POSTs the auth payload (and we've persisted it).\n * Rejects on timeout or listener error.\n */\n done: Promise<void>;\n /** Cancel the listener early (e.g. user aborted). */\n cancel: () => void;\n}\n\n/**\n * Start the browser auth flow. Does NOT open the browser itself — the caller\n * decides whether to call `open` (CLI) or hand the URL to an MCP elicitation\n * payload. Returns immediately with the URL the user should visit.\n */\nexport async function startBrowserAuthFlow(\n opts: BrowserFlowOptions = {},\n): Promise<BrowserFlowHandle> {\n const authUrl = opts.authUrl ?? \"https://auth.tempo.build\";\n const timeoutMs = opts.timeoutMs ?? 10 * 60_000;\n const bindAddress = opts.bindAddress ?? \"127.0.0.1\";\n\n let resolveDone!: () => void;\n let rejectDone!: (err: Error) => void;\n const done = new Promise<void>((resolve, reject) => {\n resolveDone = resolve;\n rejectDone = reject;\n });\n\n const server = http.createServer(async (req, res) => {\n if (\n req.method === \"POST\" &&\n req.url &&\n req.url.startsWith(\"/tempo/auth\")\n ) {\n try {\n const body = await readJson(req);\n if (\n !body ||\n typeof body.token !== \"string\" ||\n typeof body.sessionId !== \"string\" ||\n typeof body.userId !== \"string\"\n ) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"missing fields\" }));\n return;\n }\n\n await writeAuth({\n token: body.token,\n sessionId: body.sessionId,\n userId: body.userId,\n email: typeof body.email === \"string\" ? body.email : undefined,\n firstName: typeof body.firstName === \"string\" ? body.firstName : undefined,\n lastName: typeof body.lastName === \"string\" ? body.lastName : undefined,\n expiresAt: decodeJwtExp(body.token),\n lastOrgId: typeof body.orgId === \"string\" ? body.orgId : null,\n });\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.end(JSON.stringify({ ok: true }));\n // Allow the response to flush before closing.\n setImmediate(() => {\n server.close();\n resolveDone();\n });\n } catch (err) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: String(err) }));\n }\n return;\n }\n\n if (req.method === \"OPTIONS\") {\n // CORS preflight from the auth.tempo.build origin.\n res.writeHead(204, {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n });\n res.end();\n return;\n }\n\n res.writeHead(404);\n res.end();\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(opts.port ?? 0, bindAddress, () => resolve());\n });\n\n const address = server.address();\n if (!address || typeof address === \"string\") {\n server.close();\n throw new Error(\"Failed to bind callback listener\");\n }\n const callbackPort = address.port;\n // `flow=mcp` tells web-auth this sign-in came from the MCP CLI, not the\n // Electron app: web-auth runs its MCP onboarding (persona + org creation)\n // for brand-new accounts and shows terminal-appropriate success copy.\n const signInUrl = `${authUrl}/sign-in?callbackPort=${callbackPort}&flow=mcp`;\n\n const timeoutTimer = setTimeout(() => {\n server.close();\n rejectDone(new Error(`Sign-in timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n // Don't hold the process open just for the timeout\n if (typeof timeoutTimer.unref === \"function\") timeoutTimer.unref();\n\n // Clear the timeout when the user completes auth or cancels. The\n // explicit `.catch()` is required because `.finally()` returns a new\n // promise that re-throws — an unhandled rejection on that chain would\n // otherwise show up as a test/process warning.\n done.finally(() => clearTimeout(timeoutTimer)).catch(() => {});\n\n return {\n signInUrl,\n callbackPort,\n done,\n cancel: () => {\n server.close();\n rejectDone(new Error(\"Sign-in cancelled\"));\n },\n };\n}\n\nfunction readJson(req: http.IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve, reject) => {\n let raw = \"\";\n req.setEncoding(\"utf8\");\n req.on(\"data\", (chunk) => {\n raw += chunk;\n if (raw.length > 1_000_000) {\n // Defensive cap: an attacker on the local network shouldn't be able\n // to spam unbounded data at our auth listener.\n req.destroy();\n reject(new Error(\"payload too large\"));\n }\n });\n req.on(\"end\", () => {\n if (!raw) return resolve(null);\n try {\n resolve(JSON.parse(raw) as Record<string, unknown>);\n } catch (err) {\n reject(err);\n }\n });\n req.on(\"error\", reject);\n });\n}\n","/**\n * In-process MCP aggregator: ONE front server (stdio-facing) that proxies\n * tools and resources from N backend `McpServer` instances over linked\n * in-memory transports.\n *\n * This is what lets `tempo-mcp` present Tempo's whole toolset inventory\n * (issues, docs, comments, agents, scripts, slack, linear, canvas) as a\n * SINGLE MCP server entry in the host's config, while each toolset keeps\n * its existing factory (`create<X>McpServer`) completely unchanged.\n *\n * Request pinning: the factory servers snapshot org/user state per request\n * and hard-fail (`no_active_request`) when no pin is active. External hosts\n * (Claude Code, Codex) expose no turn boundaries, so the aggregator\n * synthesizes one pin per proxied tool call via the backend's\n * `onRequestStart`/`onRequestEnd` — the same contract the Electron host\n * drives per AI turn. The MCP SDK does NOT serialize request handlers, so\n * the aggregator chains every proxied call behind the previous one — at\n * most one pin (and one scope-slot value) is live at a time by\n * construction.\n */\nimport { randomUUID } from \"node:crypto\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type CallToolResult,\n type Resource,\n type Tool,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\n/** Generous in-process ceiling; canvas capture can run for minutes. */\nconst BACKEND_CALL_TIMEOUT_MS = 10 * 60 * 1000;\n\nexport interface AggregateBackend {\n /** Toolset id, e.g. \"issues\", \"docs\", \"canvas\". Used in errors/telemetry. */\n toolset: string;\n server: McpServer;\n /** Per-request pin hooks from the factory instance (see module docs). */\n onRequestStart?: (requestId: string) => void;\n onRequestEnd?: (requestId: string) => void;\n dispose?: () => void;\n}\n\n/**\n * A scope parameter the aggregator ADDS to a proxied tool's input schema\n * (required), extracts from every call, and strips before forwarding to\n * the backend. This is how external connections make org/project scoping\n * EXPLICIT per call while the factory modules keep their ambient\n * `getOrgId()`-style context — the extracted values land in\n * `onScopedCall` before the backend's request pin opens, and the host's\n * getters read them from there.\n */\nexport interface InjectedScopeParam {\n name: string;\n description: string;\n}\n\nexport interface CreateAggregateServerOptions {\n name: string;\n version: string;\n instructions?: string;\n backends: AggregateBackend[];\n /**\n * Registration filter — a tool is exposed only when this returns true.\n * This is where the external tool manifest plugs in (readonly mode,\n * `externalAllowed: false` tools, per-connection `--toolsets`).\n */\n filterTool?: (toolset: string, toolName: string) => boolean;\n /** Scope params to inject into this tool's schema (see the type doc). */\n injectParams?: (toolset: string, toolName: string) => InjectedScopeParam[];\n /**\n * Receives the extracted scope-param values of the CURRENT call, before\n * the backend pin opens. The aggregator serializes tool calls through\n * completion (the SDK does NOT — overlapping requests are possible on\n * one session), so a request-scoped slot written here is race-free.\n */\n onScopedCall?: (values: Record<string, string>) => void;\n}\n\nexport interface AggregateServer {\n /** Connect the front server to its outward transport (stdio, HTTP, ...). */\n connect: (transport: Transport) => Promise<void>;\n /** The low-level front server (exposed for tests). */\n server: Server;\n dispose: () => Promise<void>;\n}\n\ninterface ConnectedBackend extends AggregateBackend {\n client: Client;\n}\n\nexport async function createAggregateServer(\n options: CreateAggregateServerOptions,\n): Promise<AggregateServer> {\n const {\n name,\n version,\n instructions,\n backends,\n filterTool,\n injectParams,\n onScopedCall,\n } = options;\n\n const connected: ConnectedBackend[] = [];\n for (const backend of backends) {\n const [clientTransport, serverTransport] =\n InMemoryTransport.createLinkedPair();\n const client = new Client({\n name: `${name}-aggregator`,\n version,\n });\n await backend.server.connect(serverTransport);\n await client.connect(clientTransport);\n connected.push({ ...backend, client });\n }\n\n // Tool inventories are static after factory registration, so build the\n // routing table once. A name collision across toolsets is a programming\n // error (tools are prefix-namespaced) — fail loudly, don't shadow.\n const toolRoutes = new Map<string, ConnectedBackend>();\n const injectedByTool = new Map<string, InjectedScopeParam[]>();\n const toolsByBackend = new Map<string, Tool[]>();\n for (const backend of connected) {\n if (!backend.client.getServerCapabilities()?.tools) {\n toolsByBackend.set(backend.toolset, []);\n continue;\n }\n const listed = await backend.client.listTools();\n const kept: Tool[] = [];\n for (const tool of listed.tools) {\n if (filterTool && !filterTool(backend.toolset, tool.name)) continue;\n const existing = toolRoutes.get(tool.name);\n if (existing) {\n throw new Error(\n `MCP aggregate tool name collision: \"${tool.name}\" registered by both \"${existing.toolset}\" and \"${backend.toolset}\"`,\n );\n }\n toolRoutes.set(tool.name, backend);\n const injected = injectParams?.(backend.toolset, tool.name) ?? [];\n if (injected.length === 0) {\n kept.push(tool);\n continue;\n }\n injectedByTool.set(tool.name, injected);\n const schema = tool.inputSchema ?? { type: \"object\" as const };\n kept.push({\n ...tool,\n inputSchema: {\n ...schema,\n type: \"object\" as const,\n properties: {\n ...(schema.properties ?? {}),\n ...Object.fromEntries(\n injected.map((param) => [\n param.name,\n { type: \"string\", description: param.description },\n ]),\n ),\n },\n required: [\n ...new Set([\n ...((schema.required as string[] | undefined) ?? []),\n ...injected.map((param) => param.name),\n ]),\n ],\n },\n });\n }\n toolsByBackend.set(backend.toolset, kept);\n }\n\n const server = new Server(\n { name, version },\n {\n capabilities: { tools: {}, resources: {} },\n instructions,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: connected.flatMap(\n (backend) => toolsByBackend.get(backend.toolset) ?? [],\n ),\n }));\n\n // Tail of the in-flight tool-call chain (see the SERIALIZE note below).\n let callChain: Promise<void> = Promise.resolve();\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const toolName = request.params.name;\n const backend = toolRoutes.get(toolName);\n if (!backend) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n error: {\n code: \"unknown_tool\",\n message: `No registered tool named \"${toolName}\"`,\n },\n }),\n },\n ],\n } satisfies CallToolResult;\n }\n // Extract + strip the injected scope params (orgId/projectId) before\n // the backend sees the arguments; missing required scope is a\n // structured tool error naming the discovery tool to call.\n const args: Record<string, unknown> = {\n ...(request.params.arguments ?? {}),\n };\n const injected = injectedByTool.get(toolName) ?? [];\n const scopeValues: Record<string, string> = {};\n for (const param of injected) {\n const value = args[param.name];\n delete args[param.name];\n if (typeof value !== \"string\" || value.length === 0) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n error: {\n code: \"missing_scope_param\",\n message: `\"${toolName}\" requires \"${param.name}\". ${param.description}`,\n },\n }),\n },\n ],\n } satisfies CallToolResult;\n }\n scopeValues[param.name] = value;\n }\n // SERIALIZE through completion. The MCP SDK dispatches each incoming\n // request without awaiting the previous handler, so two tools/call\n // requests CAN overlap — and both the host's scope slot (fed by\n // onScopedCall) and the factories' request-snapshot maps (which fall\n // back to live getters once >1 pin is active) assume one scoped call\n // at a time. Chaining every proxied call behind the previous one makes\n // scope slot + pin + backend execution atomic per request, so a\n // concurrent org-A/org-B pair can never read each other's scope.\n const run = callChain.then(async (): Promise<CallToolResult> => {\n if (injected.length > 0) onScopedCall?.(scopeValues);\n\n const requestId = randomUUID();\n backend.onRequestStart?.(requestId);\n try {\n return (await backend.client.callTool(\n { name: toolName, arguments: args },\n undefined,\n { timeout: BACKEND_CALL_TIMEOUT_MS },\n )) as CallToolResult;\n } finally {\n backend.onRequestEnd?.(requestId);\n }\n });\n callChain = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n });\n\n // Resources: merged list, URI-routed reads. Resource URIs are\n // scheme-namespaced per toolset (e.g. issues://...), so first-listed wins\n // is never exercised in practice; reads route by exact URI, falling back\n // to a live re-list for dynamic resources.\n const listResourcesForBackend = async (\n backend: ConnectedBackend,\n ): Promise<Resource[]> => {\n const caps = backend.client.getServerCapabilities();\n if (!caps?.resources) return [];\n const listed = await backend.client.listResources();\n return listed.resources;\n };\n\n server.setRequestHandler(ListResourcesRequestSchema, async () => {\n const all = await Promise.all(connected.map(listResourcesForBackend));\n return { resources: all.flat() };\n });\n\n server.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n const uri = request.params.uri;\n for (const backend of connected) {\n const resources = await listResourcesForBackend(backend);\n if (resources.some((resource) => resource.uri === uri)) {\n return backend.client.readResource({ uri });\n }\n }\n throw new Error(`Unknown resource: ${uri}`);\n });\n\n return {\n server,\n connect: (transport) => server.connect(transport),\n dispose: async () => {\n for (const backend of connected) {\n await backend.client.close().catch(() => {});\n backend.dispose?.();\n }\n await server.close().catch(() => {});\n },\n };\n}\n","/**\n * Process-level safety net for standalone MCP binaries.\n *\n * MCP-over-stdio is a long-running subprocess that Claude Code spawns and\n * communicates with via JSON-RPC. If our process crashes, the host marks\n * the server \"failed\" for the remainder of the session — there is no\n * client-side reconnect in the MCP protocol. So we install handlers that:\n *\n * - log uncaught exceptions / unhandled rejections to `~/.tempo/logs/`\n * so a user can paste the trace into a bug report\n * - swallow EPIPE on stdout (Claude Code occasionally pauses pipes\n * during ctrl-C; killing the process on EPIPE is a self-inflicted\n * disconnect)\n * - exit cleanly on SIGTERM / SIGINT so the host's \"server stopped\"\n * state is correct\n *\n * Errors that legitimately should crash (out of memory, fatal SDK bugs)\n * still crash — these handlers ONLY guard against the common transient\n * failure modes that today silently take MCP servers offline.\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { logFilePath, logsDir } from \"../paths.ts\";\n\nexport interface ProcessSafetyOptions {\n /** Identifier for log naming. Defaults to `process.argv[1]` basename. */\n binaryName?: string;\n /** Optional shutdown hook called on SIGTERM/SIGINT before exit. */\n onShutdown?: () => Promise<void> | void;\n}\n\nexport function installProcessSafety(opts: ProcessSafetyOptions = {}): () => void {\n const binaryName =\n opts.binaryName ?? path.basename(process.argv[1] ?? \"tempo-mcp\");\n\n const logError = (label: string, err: unknown) => {\n const line = `[${new Date().toISOString()}] ${label}: ${\n err instanceof Error ? err.stack || err.message : String(err)\n }\\n`;\n // Best-effort: never let logging block or crash the process.\n try {\n fs.mkdirSync(logsDir(), { recursive: true, mode: 0o700 });\n fs.appendFileSync(logFilePath(binaryName), line, { mode: 0o600 });\n } catch {\n // last resort: stderr (which the host captures)\n try {\n process.stderr.write(line);\n } catch {\n // give up\n }\n }\n };\n\n const onUncaught = (err: Error) => logError(\"uncaughtException\", err);\n const onUnhandled = (reason: unknown) => logError(\"unhandledRejection\", reason);\n\n process.on(\"uncaughtException\", onUncaught);\n process.on(\"unhandledRejection\", onUnhandled);\n\n // Swallow EPIPE on stdout — Claude Code occasionally pauses our pipe.\n // We can't write to stdout anyway in that case; throwing would be fatal.\n const stdoutError = (err: NodeJS.ErrnoException) => {\n if (err.code !== \"EPIPE\") logError(\"stdout-error\", err);\n };\n process.stdout.on(\"error\", stdoutError);\n\n const onSignal = (signal: NodeJS.Signals) => {\n Promise.resolve(opts.onShutdown?.())\n .catch((err) => logError(`shutdown:${signal}`, err))\n .finally(() => {\n // Use a small delay to let in-flight stdout writes flush before\n // the host kills us.\n setTimeout(() => process.exit(0), 50);\n });\n };\n process.once(\"SIGTERM\", () => onSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", () => onSignal(\"SIGINT\"));\n\n return () => {\n process.off(\"uncaughtException\", onUncaught);\n process.off(\"unhandledRejection\", onUnhandled);\n process.stdout.off(\"error\", stdoutError);\n };\n}\n\n/**\n * Test helper — verify the log file path exists (and optionally has a\n * non-empty body). Returns the resolved path or null.\n */\nexport async function findLatestLogFile(binaryName: string): Promise<string | null> {\n try {\n const entries = await fsp.readdir(logsDir());\n const matching = entries.filter((e) => e.startsWith(`mcp-${binaryName}-`));\n if (matching.length === 0) return null;\n matching.sort();\n return path.join(logsDir(), matching[matching.length - 1]);\n } catch {\n return null;\n }\n}\n","/**\n * Per-tool-call request-id pin synthesis.\n *\n * Tempo's factory-mcp and docs-mcp expose `onRequestStart(id)` /\n * `onRequestEnd(id)` lifecycle hooks that the in-app SessionManager fires at\n * AI request boundaries (so all tool calls in one AI turn see a coherent\n * (orgId, boardId) snapshot). Standalone MCP hosts (Claude Code, Codex CLI)\n * don't expose those boundaries — they just dispatch `tools/call` requests\n * over JSON-RPC.\n *\n * This interceptor synthesizes a pin per tool-call:\n *\n * 1. Wraps `server.server.setRequestHandler` so we observe `tools/call`.\n * 2. Before the underlying handler runs, fire `onRequestStart(uuid)`.\n * 3. In `finally`, fire `onRequestEnd(uuid)`.\n *\n * stdio MCP servers process JSON-RPC requests serially per spec, so the\n * `RequestSnapshotMap.size <= 1` invariant in factory-mcp continues to hold.\n *\n * The interceptor must be installed AFTER all tools are registered (because\n * `McpServer.tool()` registers handlers internally, and we need the final\n * `tools/call` handler to wrap).\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nexport interface PinLifecycle {\n onRequestStart: (requestId: string) => void;\n onRequestEnd: (requestId: string) => void;\n}\n\nexport interface InstallPinInterceptorOptions {\n /** The factory/docs MCP instance providing the lifecycle hooks. */\n lifecycle: PinLifecycle;\n /** Optional fixed prefix on synthesized request ids — useful in logs. */\n idPrefix?: string;\n}\n\n/**\n * Install the per-tool-call pin interceptor. Returns an `uninstall` fn for\n * tests or hot-reload scenarios.\n *\n * Implementation note: the MCP SDK exposes a `server` (the underlying\n * Server instance) under `.server` on `McpServer`. We intercept its\n * `setRequestHandler` by wrapping the `tools/call` handler at registration\n * time. Because `McpServer.tool()` registers `tools/call` itself, we need\n * to install the interceptor *after* that registration — which means\n * doing it just before `server.connect(transport)` in the binary\n * entrypoint.\n */\nexport function installPinInterceptor(\n mcpServer: McpServer,\n opts: InstallPinInterceptorOptions,\n): () => void {\n // McpServer wraps an underlying Server from @modelcontextprotocol/sdk.\n // The `.server` accessor surfaces the underlying Server (typed as `unknown`\n // here to avoid a hard dep on the SDK's internal types).\n const underlying = (mcpServer as unknown as { server: ServerLike }).server;\n if (!underlying || typeof underlying.setRequestHandler !== \"function\") {\n throw new Error(\n \"installPinInterceptor: McpServer has no .server.setRequestHandler — SDK shape changed?\",\n );\n }\n\n // Capture the current `tools/call` handler then re-register a wrapped one.\n // The SDK keeps handlers in a Map keyed by method; setting the same key\n // overrides. We pull the existing handler via the request-handlers map\n // exposed (in SDK 1.27.x) at `_requestHandlers`. If the field name shifts,\n // we fall back to a no-op that calls the wrapped handler directly.\n type Handler = (req: unknown, extra: unknown) => Promise<unknown>;\n const handlers = (underlying as ServerLike & { _requestHandlers?: Map<string, Handler> })\n ._requestHandlers;\n\n const original = handlers?.get(\"tools/call\");\n if (!original) {\n // No tools registered yet, or SDK shape changed. We can still attach\n // a wrapping handler — calls without a registered tool will surface\n // the SDK's normal MethodNotFound error.\n return () => {};\n }\n\n const idPrefix = opts.idPrefix ?? \"mcp\";\n const wrapped: Handler = async (req, extra) => {\n const requestId = `${idPrefix}-${randomUUID()}`;\n try {\n opts.lifecycle.onRequestStart(requestId);\n } catch {\n // Listener errors must not break the request\n }\n try {\n return await original(req, extra);\n } finally {\n try {\n opts.lifecycle.onRequestEnd(requestId);\n } catch {\n // Listener errors must not break the request\n }\n }\n };\n\n handlers!.set(\"tools/call\", wrapped);\n\n return () => {\n if (handlers?.get(\"tools/call\") === wrapped) {\n handlers.set(\"tools/call\", original);\n }\n };\n}\n\n/**\n * Minimal type for the underlying SDK Server we touch — kept narrow so the\n * SDK's full type surface doesn't leak into mcp-runtime consumers.\n */\ninterface ServerLike {\n setRequestHandler: unknown;\n // The actual handler map; existence is verified at runtime.\n _requestHandlers?: Map<\n string,\n (req: unknown, extra: unknown) => Promise<unknown>\n >;\n}\n","/**\n * MCP elicitation-driven sign-in flow.\n *\n * When an MCP starts (or a tool call hits a 401), we want the host\n * (Claude Code, Codex CLI) to prompt the user to sign in. The MCP spec\n * has an `elicitation/create` capability — the server can ask the host\n * to surface a message to the user.\n *\n * Today, real elicitation-prompt support varies by host. As a fallback,\n * we ALSO return the sign-in URL as a tool error so the user can copy/paste\n * it manually. This pairs the slick path (elicitation) with a reliable\n * fallback (URL in the tool result text).\n *\n * Implementation detail: the @modelcontextprotocol/sdk supports the\n * elicitation request via `server.server.createMessage` / equivalent. We\n * wrap that surface in a stable API so the binary entrypoints don't depend\n * on SDK shape.\n */\n\nimport { startBrowserAuthFlow, type BrowserFlowHandle } from \"../auth/browser-flow.ts\";\nimport { readAuth } from \"../auth/token-store.ts\";\n\nexport interface AuthElicitationOptions {\n authUrl?: string;\n timeoutMs?: number;\n}\n\nexport interface AuthElicitationResult {\n /** The user-visible URL — surfaced via elicitation OR in tool error text. */\n signInUrl: string;\n /** Resolves once the user has signed in (or rejects on timeout/cancel). */\n waitForSignIn: () => Promise<void>;\n /** Cancel the listener (e.g. tool call was aborted). */\n cancel: () => void;\n}\n\n/**\n * Returns an `auth_required` elicitation payload AND a localhost listener\n * primed to receive the eventual sign-in. The caller picks how to surface\n * the URL (elicitation, tool error text, etc).\n *\n * Call this from a tool handler when `getToken()` throws \"Not authenticated\"\n * — return the message to the host AND in parallel await `waitForSignIn()`\n * so the tool can retry once auth lands.\n */\nexport async function requestAuthElicitation(\n opts: AuthElicitationOptions = {},\n): Promise<AuthElicitationResult> {\n const handle: BrowserFlowHandle = await startBrowserAuthFlow({\n authUrl: opts.authUrl,\n timeoutMs: opts.timeoutMs ?? 5 * 60_000,\n });\n\n return {\n signInUrl: handle.signInUrl,\n waitForSignIn: () => handle.done,\n cancel: () => handle.cancel(),\n };\n}\n\n/**\n * One-shot helper for binary entrypoints: returns immediately if auth\n * already exists, otherwise blocks until the user completes sign-in via\n * the browser handoff. Used by `tempo-mcp login` and by an MCP binary's\n * startup probe.\n */\nexport async function ensureAuth(opts: AuthElicitationOptions = {}): Promise<{\n hadExistingAuth: boolean;\n signInUrl?: string;\n}> {\n const existing = await readAuth();\n if (existing && existing.expiresAt - Date.now() > 60_000) {\n return { hadExistingAuth: true };\n }\n const eli = await requestAuthElicitation(opts);\n await eli.waitForSignIn();\n return { hadExistingAuth: false, signInUrl: eli.signInUrl };\n}\n","/**\n * Environment + workspace resolution for `tempo-mcp serve`.\n *\n * Convex endpoints default to production so `npx @tempo-ai/mcp` works with\n * zero configuration; TEMPO_CONVEX_URL / TEMPO_CONVEX_SITE_URL override\n * for previews and staging.\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport const DEFAULT_CONVEX_URL = \"https://greedy-jackal-526.convex.cloud\";\n\nexport function resolveConvexUrl(): string {\n return process.env.TEMPO_CONVEX_URL ?? DEFAULT_CONVEX_URL;\n}\n\nexport function resolveConvexSiteUrl(convexUrl: string): string {\n const explicit = process.env.TEMPO_CONVEX_SITE_URL;\n if (explicit) return explicit;\n return convexUrl.replace(/\\.convex\\.cloud$/, \".convex.site\");\n}\n\nexport const ALL_TOOLSETS = [\n \"issues\",\n \"docs\",\n \"comments\",\n \"agents\",\n \"scripts\",\n \"slack\",\n \"linear\",\n \"canvas\",\n] as const;\n\nexport type ToolsetId = (typeof ALL_TOOLSETS)[number];\n\nexport interface ServeOptions {\n toolsets: ToolsetId[];\n readonly: boolean;\n}\n\n/**\n * NOTE: org/project scope is deliberately NOT configurable here — the AI\n * passes an explicit `orgId`/`projectId` on every scoped tool call\n * (discovered via tempo_list_orgs / tempo_list_projects). The only flags\n * are capability-shaping: which toolsets, and readonly.\n */\nexport function parseServeArgs(args: string[]): ServeOptions | { error: string } {\n let toolsets: ToolsetId[] = [...ALL_TOOLSETS];\n let readonly = process.env.TEMPO_MCP_READONLY === \"1\";\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"--readonly\") {\n readonly = true;\n } else if (arg === \"--toolsets\") {\n const value = args[++i];\n if (!value) return { error: \"--toolsets requires a comma-separated list\" };\n const requested = value.split(\",\").map((t) => t.trim()).filter(Boolean);\n const unknown = requested.filter(\n (t) => !ALL_TOOLSETS.includes(t as ToolsetId),\n );\n if (unknown.length) {\n return {\n error: `Unknown toolset(s): ${unknown.join(\", \")}. Valid: ${ALL_TOOLSETS.join(\", \")}`,\n };\n }\n toolsets = requested as ToolsetId[];\n } else {\n return { error: `Unknown flag: ${arg}` };\n }\n }\n return { toolsets, readonly };\n}\n\nexport interface DiscoveredWorkspace {\n workspaceRoot: string;\n canvasesDir: string;\n}\n\n/**\n * Walk upward from `startDir` looking for `tempo/tempo.config.json` — the\n * marker of a Tempo-canvas-enabled repo. The canvas toolset registers only\n * when this resolves; in any other cwd the canvas tools simply don't exist.\n */\nexport function discoverWorkspace(\n startDir = process.cwd(),\n): DiscoveredWorkspace | null {\n let dir = path.resolve(startDir);\n for (;;) {\n const configPath = path.join(dir, \"tempo\", \"tempo.config.json\");\n if (fs.existsSync(configPath)) {\n let canvasesRel = \"./designs\";\n try {\n const parsed = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as {\n paths?: { canvases?: string };\n };\n if (parsed.paths?.canvases) canvasesRel = parsed.paths.canvases;\n } catch {\n // Unparseable config — fall back to the conventional location.\n }\n return {\n workspaceRoot: dir,\n canvasesDir: path.resolve(dir, \"tempo\", canvasesRel),\n };\n }\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n","import { ConvexHttpClient } from \"convex/browser\";\nimport type { FunctionReference, FunctionArgs, FunctionReturnType } from \"convex/server\";\nimport { mapConvexError, McpToolError, type McpError } from \"./errors.ts\";\n\nexport interface AuthToken {\n jwt: string;\n expiresAt: number;\n}\n\nexport interface AuthProvider {\n getToken(): Promise<AuthToken>;\n}\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 200;\n\nexport class AuthedConvexClient {\n private client: ConvexHttpClient;\n private authProvider: AuthProvider;\n private cachedToken: AuthToken | null = null;\n\n constructor(opts: { url: string; authProvider: AuthProvider }) {\n this.client = new ConvexHttpClient(opts.url);\n this.authProvider = opts.authProvider;\n }\n\n async query<F extends FunctionReference<\"query\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.query(fn, args));\n }\n\n async mutation<F extends FunctionReference<\"mutation\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.mutation(fn, args));\n }\n\n async action<F extends FunctionReference<\"action\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.action(fn, args));\n }\n\n private async ensureAuth(): Promise<void> {\n const now = Date.now();\n if (this.cachedToken && this.cachedToken.expiresAt - now > 30_000) {\n this.client.setAuth(this.cachedToken.jwt);\n return;\n }\n const token = await this.authProvider.getToken();\n this.cachedToken = token;\n this.client.setAuth(token.jwt);\n }\n\n private async callWithAuth<T>(fn: () => Promise<T>): Promise<T> {\n await this.ensureAuth();\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n return await fn();\n } catch (err: unknown) {\n const mapped = mapConvexError(err);\n\n if (mapped.code === \"auth_required\" && attempt === 0) {\n this.cachedToken = null;\n await this.ensureAuth();\n continue;\n }\n\n if (mapped.code === \"internal\" && attempt < MAX_RETRIES - 1) {\n await sleep(BASE_DELAY_MS * 2 ** attempt);\n continue;\n }\n\n throw new McpToolError(mapped);\n }\n }\n\n throw new McpToolError({\n code: \"internal\",\n message: \"Max retries exceeded\",\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","export interface McpError {\n code:\n | \"auth_required\"\n | \"forbidden\"\n | \"not_found\"\n | \"invalid_args\"\n | \"batch_too_large\"\n | \"confirmation_required\"\n | \"duplicate\"\n | \"rate_limited\"\n | \"branch_not_pushed\"\n | \"internal\";\n message: string;\n details?: Record<string, unknown>;\n}\n\nexport class McpToolError extends Error {\n public readonly error: McpError;\n constructor(error: McpError) {\n super(error.message);\n this.name = \"McpToolError\";\n this.error = error;\n }\n}\n\nexport function mapConvexError(err: unknown): McpError {\n const message =\n err instanceof Error ? err.message : String(err);\n\n if (message.includes(\"Not authenticated\") || message.includes(\"Unauthenticated\")) {\n return { code: \"auth_required\", message: \"Authentication required. Please sign in.\" };\n }\n\n if (message.includes(\"not a member\") || message.includes(\"Unauthorized\") || message.includes(\"forbidden\")) {\n return { code: \"forbidden\", message: \"You are not a member of this organization.\" };\n }\n\n if (message.includes(\"not found\") || message.includes(\"Could not find\")) {\n return { code: \"not_found\", message };\n }\n\n if (message.includes(\"rate limit\") || message.includes(\"Too many requests\")) {\n return { code: \"rate_limited\", message: \"Rate limited. Please retry after a moment.\" };\n }\n\n if (message.includes(\"Invalid argument\") || message.includes(\"Validator error\")) {\n return { code: \"invalid_args\", message };\n }\n\n return { code: \"internal\", message };\n}\n","/**\n * Shared auth entry for every CLI code path (TEM-2121).\n *\n * Two ways in, ONE stored shape:\n * - `TEMPO_AUTH_TOKEN` set (headless cloud sandboxes) → exchange it at\n * `/auth/token-exchange` for a fresh Clerk session + JWT.\n * - Otherwise → the browser sign-in handoff (`ensureAuth`).\n *\n * Both write the standard auth.json, so the refresh loop, AuthProvider,\n * and every Convex client work identically afterwards.\n */\nimport {\n AuthedConvexClient,\n type AuthProvider,\n} from \"@tempo-modules/convex-http-client-auth\";\nimport {\n createAuthProvider,\n createRefreshLoop,\n ensureAuth,\n envAccessToken,\n exchangeAccessToken,\n readAuth,\n storedAuthMatchesAccessToken,\n type RefreshLoopHandle,\n} from \"@tempo-modules/mcp-runtime\";\nimport { resolveConvexSiteUrl, resolveConvexUrl } from \"./config.ts\";\n\n/**\n * Ensure usable credentials exist, preferring the env token when set.\n *\n * With `TEMPO_AUTH_TOKEN` present the env token is AUTHORITATIVE: cached\n * credentials are only reused when their fingerprint proves they came from\n * this exact token. A cache left by a browser login or a DIFFERENT token\n * (shared cloud home dirs) is re-exchanged, never silently run as the\n * other user. And we never fall back to an interactive browser flow that\n * can't complete in a sandbox.\n */\nexport async function ensureCliAuth(): Promise<void> {\n const accessToken = envAccessToken();\n const convexSiteUrl = resolveConvexSiteUrl(resolveConvexUrl());\n if (!accessToken) {\n // Cold start with a stale JWT but a live session (the normal state —\n // hosts relaunch `serve` long after the short-lived JWT expired):\n // refresh silently via the stored sessionId FIRST. Only a genuinely\n // dead/absent session falls through to the browser flow — without\n // this, every morning's first host launch popped a sign-in browser.\n const existing = await readAuth();\n if (existing && existing.expiresAt - Date.now() <= 60_000) {\n const loop = createRefreshLoop({\n convexSiteUrl,\n onReauthRequired: () => {},\n });\n await loop.refreshNow();\n }\n await ensureAuth();\n return;\n }\n const existing = await readAuth();\n if (\n existing &&\n storedAuthMatchesAccessToken(existing, accessToken) &&\n existing.expiresAt - Date.now() > 60_000\n ) {\n return;\n }\n await exchangeAccessToken({ convexSiteUrl, accessToken });\n}\n\n/**\n * The refresh loop's headless recovery hook: re-exchange the env token\n * when the current session dies mid-run. No env token → no recovery\n * (returns false and the loop surfaces reauth-required as before).\n */\nexport function envTokenReauth(convexSiteUrl: string): () => Promise<boolean> {\n return async () => {\n const accessToken = envAccessToken();\n if (!accessToken) return false;\n await exchangeAccessToken({ convexSiteUrl, accessToken });\n return true;\n };\n}\n\nexport interface CliConvexHandle {\n convex: AuthedConvexClient;\n authProvider: AuthProvider;\n dispose: () => void;\n}\n\n/**\n * Auth-wired Convex client for short-lived subcommands (`token …`). The\n * caller must `dispose()` so the refresh timer doesn't hold the process\n * open.\n */\nexport function createCliConvex(): CliConvexHandle {\n const convexUrl = resolveConvexUrl();\n const convexSiteUrl = resolveConvexSiteUrl(convexUrl);\n const refreshLoop: RefreshLoopHandle = createRefreshLoop({\n convexSiteUrl,\n attemptReauth: envTokenReauth(convexSiteUrl),\n onReauthRequired: () => {\n console.error(\n \"[tempo-mcp] Tempo session expired. Run `tempo-mcp login` to sign in again.\",\n );\n },\n });\n refreshLoop.start();\n const authProvider = createAuthProvider(refreshLoop);\n const convex = new AuthedConvexClient({ url: convexUrl, authProvider });\n return { convex, authProvider, dispose: () => refreshLoop.stop() };\n}\n"],"mappings":"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","names":["path","path","sleep","fs","fsp","path","path","fs","randomUUID","fs","path","sleep","existing"]}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
run
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-6P4PI7CS.js";
|
|
5
|
+
import "./chunk-KQNQSVOA.js";
|
|
6
6
|
export {
|
|
7
7
|
run
|
|
8
8
|
};
|