@tempo-ai/mcp 0.0.100-staging.1

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.
@@ -0,0 +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/browser-flow.ts","../../mcp-runtime/aggregate/aggregator.ts","../../mcp-runtime/safety/process-handlers.ts","../../mcp-runtime/elicitation/auth-elicitation.ts","../../mcp-runtime/transport/pin-interceptor.ts","../src/config.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\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 /** 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 async function refreshNow(): Promise<string | null> {\n const current = await readAuth();\n if (!current) {\n opts.onReauthRequired();\n return null;\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.\n await clearAuth();\n opts.onReauthRequired();\n return null;\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 opts.onReauthRequired();\n return null;\n }\n\n const body = (await res.json()) as { token?: string };\n if (!body.token) {\n opts.onReauthRequired();\n return null;\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 opts.onReauthRequired();\n return null;\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 * 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 const signInUrl = `${authUrl}/sign-in?callbackPort=${callbackPort}`;\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 * 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 * 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 * 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"],"mappings":"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","names":["path","path","fs","fsp","path","path","fs","randomUUID","fs","path"]}
@@ -0,0 +1,156 @@
1
+ import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
2
+ import {
3
+ clearAuth,
4
+ parseServeArgs,
5
+ readAuth,
6
+ startBrowserAuthFlow
7
+ } from "./chunk-JDPG7F4Z.js";
8
+
9
+ // src/help.ts
10
+ var USAGE = `tempo-mcp <subcommand>
11
+
12
+ Tempo's MCP server \u2014 issues, docs, comments, agents, scripts, Slack,
13
+ Linear, and (inside a Tempo repo) canvas tools, served as ONE stdio MCP
14
+ server.
15
+
16
+ MCP host config (Claude Code / Codex / Cursor):
17
+ { "tempo": { "command": "npx", "args": ["-y", "@tempo-ai/mcp"] } }
18
+
19
+ Scoping: the AI passes an explicit orgId (and projectId where
20
+ project-scoped) on every tool call, discovered via the built-in
21
+ tempo_list_orgs / tempo_list_projects tools. Nothing org-related is
22
+ configured on the connection.
23
+
24
+ Server:
25
+ (default) | serve Serve the aggregate MCP server over stdio
26
+ --toolsets a,b Only these toolsets (issues,docs,comments,agents,
27
+ scripts,slack,linear,canvas). Default: all.
28
+ --readonly Register no write tools at all
29
+
30
+ Auth (identity only):
31
+ login Sign in via the browser (stores ~/.tempo/auth.json)
32
+ logout Clear stored credentials
33
+ whoami Show the signed-in user
34
+
35
+ Environment:
36
+ TEMPO_CONVEX_URL Override the Convex deployment (default: production)
37
+ TEMPO_MCP_READONLY=1 Same as --readonly
38
+ `;
39
+
40
+ // src/login.ts
41
+ import { exec } from "child_process";
42
+ import { promisify } from "util";
43
+ var execAsync = promisify(exec);
44
+ async function openInBrowser(url) {
45
+ const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
46
+ try {
47
+ await execAsync(cmd, { windowsHide: true });
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ async function runLogin() {
54
+ const existing = await readAuth();
55
+ if (existing && existing.expiresAt - Date.now() > 6e4) {
56
+ console.log(
57
+ `Already signed in as ${existing.email ?? existing.userId}. Run \`tempo-mcp logout\` first to re-authenticate.`
58
+ );
59
+ return 0;
60
+ }
61
+ const handle = await startBrowserAuthFlow({
62
+ authUrl: process.env.TEMPO_AUTH_URL
63
+ });
64
+ const opened = process.env.TEMPO_MCP_NO_BROWSER === "1" ? false : await openInBrowser(handle.signInUrl);
65
+ if (opened) {
66
+ console.log("Opened your browser to sign in to Tempo.");
67
+ console.log("If a browser didn't open, visit this URL:");
68
+ } else {
69
+ console.log("Sign in to Tempo by visiting:");
70
+ }
71
+ console.log(` ${handle.signInUrl}`);
72
+ console.log("");
73
+ console.log("Waiting for sign-in to complete...");
74
+ try {
75
+ await handle.done;
76
+ const stored = await readAuth();
77
+ console.log("");
78
+ console.log(
79
+ `Signed in as ${stored?.email ?? stored?.userId ?? "unknown user"}.`
80
+ );
81
+ console.log(`Credentials stored in ~/.tempo/auth.json (0600).`);
82
+ return 0;
83
+ } catch (err) {
84
+ console.error("Sign-in failed:", err instanceof Error ? err.message : err);
85
+ return 1;
86
+ }
87
+ }
88
+
89
+ // src/whoami.ts
90
+ async function runWhoami() {
91
+ const auth = await readAuth();
92
+ if (!auth) {
93
+ console.log("Not signed in. Run `tempo-mcp login`.");
94
+ return 1;
95
+ }
96
+ const display = auth.email ? `${auth.email} (${auth.userId})` : auth.userId;
97
+ console.log(`Signed in as: ${display}`);
98
+ const ttlMs = auth.expiresAt - Date.now();
99
+ if (ttlMs <= 0) {
100
+ console.log(
101
+ `Token state: expired ${Math.round(-ttlMs / 1e3)}s ago (refresh on next call)`
102
+ );
103
+ } else {
104
+ console.log(`Token state: fresh, expires in ${Math.round(ttlMs / 1e3)}s`);
105
+ }
106
+ return 0;
107
+ }
108
+ async function runLogout() {
109
+ await clearAuth();
110
+ console.log("Signed out. Run `tempo-mcp login` to sign in again.");
111
+ return 0;
112
+ }
113
+
114
+ // src/index.ts
115
+ async function run(argv) {
116
+ const [, , subcommand, ...rest] = argv;
117
+ if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
118
+ process.stdout.write(USAGE);
119
+ return 0;
120
+ }
121
+ try {
122
+ if (!subcommand || subcommand === "serve" || subcommand.startsWith("--")) {
123
+ const serveArgs = !subcommand || subcommand === "serve" ? rest : [subcommand, ...rest];
124
+ const options = parseServeArgs(serveArgs);
125
+ if ("error" in options) {
126
+ console.error(options.error);
127
+ process.stderr.write(USAGE);
128
+ return 1;
129
+ }
130
+ const { runServe } = await import("./serve-OI2RGYVP.js");
131
+ return await runServe(options);
132
+ }
133
+ switch (subcommand) {
134
+ case "login":
135
+ return await runLogin();
136
+ case "logout":
137
+ return await runLogout();
138
+ case "whoami":
139
+ return await runWhoami();
140
+ default:
141
+ console.error(`Unknown subcommand: ${subcommand}`);
142
+ process.stderr.write(USAGE);
143
+ return 1;
144
+ }
145
+ } catch (err) {
146
+ console.error(
147
+ err instanceof Error ? err.stack || err.message : String(err)
148
+ );
149
+ return 1;
150
+ }
151
+ }
152
+
153
+ export {
154
+ run
155
+ };
156
+ //# sourceMappingURL=chunk-QTTJRK4J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/help.ts","../src/login.ts","../src/whoami.ts","../src/index.ts"],"sourcesContent":["export const USAGE = `tempo-mcp <subcommand>\n\nTempo's MCP server — issues, docs, comments, agents, scripts, Slack,\nLinear, and (inside a Tempo repo) canvas tools, served as ONE stdio MCP\nserver.\n\nMCP host config (Claude Code / Codex / Cursor):\n { \"tempo\": { \"command\": \"npx\", \"args\": [\"-y\", \"@tempo-ai/mcp\"] } }\n\nScoping: the AI passes an explicit orgId (and projectId where\nproject-scoped) on every tool call, discovered via the built-in\ntempo_list_orgs / tempo_list_projects tools. Nothing org-related is\nconfigured on the connection.\n\nServer:\n (default) | serve Serve the aggregate MCP server over stdio\n --toolsets a,b Only these toolsets (issues,docs,comments,agents,\n scripts,slack,linear,canvas). Default: all.\n --readonly Register no write tools at all\n\nAuth (identity only):\n login Sign in via the browser (stores ~/.tempo/auth.json)\n logout Clear stored credentials\n whoami Show the signed-in user\n\nEnvironment:\n TEMPO_CONVEX_URL Override the Convex deployment (default: production)\n TEMPO_MCP_READONLY=1 Same as --readonly\n`;\n","/**\n * `tempo-mcp login` — open the browser, wait for the user to sign in, store\n * credentials in ~/.tempo/auth.json. Reuses the same localhost-callback\n * primitive that the Electron app uses (web-auth Callback.tsx POSTs to\n * `127.0.0.1:{port}/tempo/auth`).\n *\n * Returns an exit code — see `whoami.ts` for the rationale.\n */\n\nimport { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { startBrowserAuthFlow, readAuth } from \"@tempo-modules/mcp-runtime\";\n\nconst execAsync = promisify(exec);\n\n/**\n * Cross-platform \"open this URL in the user's default browser\". macOS uses\n * `open`, Linux `xdg-open`, Windows `start`. Falls back to printing the\n * URL if none of those exist (e.g. a headless server).\n */\nasync function openInBrowser(url: string): Promise<boolean> {\n const cmd =\n process.platform === \"darwin\"\n ? `open \"${url}\"`\n : process.platform === \"win32\"\n ? `start \"\" \"${url}\"`\n : `xdg-open \"${url}\"`;\n try {\n await execAsync(cmd, { windowsHide: true });\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function runLogin(): Promise<number> {\n const existing = await readAuth();\n if (existing && existing.expiresAt - Date.now() > 60_000) {\n console.log(\n `Already signed in as ${existing.email ?? existing.userId}. Run \\`tempo-mcp logout\\` first to re-authenticate.`,\n );\n return 0;\n }\n\n // TEMPO_AUTH_URL redirects the sign-in page (previews, e2e);\n // TEMPO_MCP_NO_BROWSER=1 suppresses the browser launch (headless/e2e).\n const handle = await startBrowserAuthFlow({\n authUrl: process.env.TEMPO_AUTH_URL,\n });\n const opened =\n process.env.TEMPO_MCP_NO_BROWSER === \"1\"\n ? false\n : await openInBrowser(handle.signInUrl);\n\n if (opened) {\n console.log(\"Opened your browser to sign in to Tempo.\");\n console.log(\"If a browser didn't open, visit this URL:\");\n } else {\n console.log(\"Sign in to Tempo by visiting:\");\n }\n console.log(` ${handle.signInUrl}`);\n console.log(\"\");\n console.log(\"Waiting for sign-in to complete...\");\n\n try {\n await handle.done;\n const stored = await readAuth();\n console.log(\"\");\n console.log(\n `Signed in as ${stored?.email ?? stored?.userId ?? \"unknown user\"}.`,\n );\n console.log(`Credentials stored in ~/.tempo/auth.json (0600).`);\n return 0;\n } catch (err) {\n console.error(\"Sign-in failed:\", err instanceof Error ? err.message : err);\n return 1;\n }\n}\n","import { readAuth, clearAuth } from \"@tempo-modules/mcp-runtime\";\n\n/**\n * Subcommand handlers return an exit code (0 = success, 1 = error). The\n * dispatcher in `index.ts` is responsible for actually calling process.exit\n * — keeping it out of the handlers makes them unit-testable without\n * stubbing process.exit.\n */\n\nexport async function runWhoami(): Promise<number> {\n const auth = await readAuth();\n if (!auth) {\n console.log(\"Not signed in. Run `tempo-mcp login`.\");\n return 1;\n }\n const display = auth.email\n ? `${auth.email} (${auth.userId})`\n : auth.userId;\n console.log(`Signed in as: ${display}`);\n const ttlMs = auth.expiresAt - Date.now();\n if (ttlMs <= 0) {\n console.log(\n `Token state: expired ${Math.round(-ttlMs / 1000)}s ago (refresh on next call)`,\n );\n } else {\n console.log(`Token state: fresh, expires in ${Math.round(ttlMs / 1000)}s`);\n }\n return 0;\n}\n\nexport async function runLogout(): Promise<number> {\n await clearAuth();\n console.log(\"Signed out. Run `tempo-mcp login` to sign in again.\");\n return 0;\n}\n","/**\n * Tempo MCP CLI entrypoint.\n *\n * The DEFAULT command (no subcommand) serves Tempo's full MCP tool surface\n * over stdio — that's what an MCP host config invokes:\n * { \"tempo\": { \"command\": \"npx\", \"args\": [\"-y\", \"@tempo-ai/mcp\"] } }\n *\n * Subcommand routing:\n * (none) | serve [--toolsets a,b] [--readonly] [--org id] [--project id]\n * → run the aggregate MCP server over stdio (long-running)\n * login | logout | whoami | orgs → auth utilities (short-lived)\n * --help | -h → print usage\n */\n\nimport { USAGE } from \"./help.ts\";\nimport { runLogin } from \"./login.ts\";\nimport { runLogout, runWhoami } from \"./whoami.ts\";\nimport { parseServeArgs } from \"./config.ts\";\n\nexport async function run(argv: string[]): Promise<number> {\n const [, , subcommand, ...rest] = argv;\n\n if (subcommand === \"--help\" || subcommand === \"-h\" || subcommand === \"help\") {\n process.stdout.write(USAGE);\n return 0;\n }\n\n try {\n // Bare invocation and flag-only invocations serve; `serve` is the\n // explicit spelling.\n if (\n !subcommand ||\n subcommand === \"serve\" ||\n subcommand.startsWith(\"--\")\n ) {\n const serveArgs =\n !subcommand || subcommand === \"serve\"\n ? rest\n : [subcommand, ...rest];\n const options = parseServeArgs(serveArgs);\n if (\"error\" in options) {\n console.error(options.error);\n process.stderr.write(USAGE);\n return 1;\n }\n const { runServe } = await import(\"./serve.ts\");\n return await runServe(options);\n }\n\n switch (subcommand) {\n case \"login\":\n return await runLogin();\n case \"logout\":\n return await runLogout();\n case \"whoami\":\n return await runWhoami();\n default:\n console.error(`Unknown subcommand: ${subcommand}`);\n process.stderr.write(USAGE);\n return 1;\n }\n } catch (err) {\n console.error(\n err instanceof Error ? err.stack || err.message : String(err),\n );\n return 1;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Tempo MCP CLI entrypoint.
3
+ *
4
+ * The DEFAULT command (no subcommand) serves Tempo's full MCP tool surface
5
+ * over stdio — that's what an MCP host config invokes:
6
+ * { "tempo": { "command": "npx", "args": ["-y", "@tempo-ai/mcp"] } }
7
+ *
8
+ * Subcommand routing:
9
+ * (none) | serve [--toolsets a,b] [--readonly] [--org id] [--project id]
10
+ * → run the aggregate MCP server over stdio (long-running)
11
+ * login | logout | whoami | orgs → auth utilities (short-lived)
12
+ * --help | -h → print usage
13
+ */
14
+ declare function run(argv: string[]): Promise<number>;
15
+
16
+ export { run };
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
2
+ import {
3
+ run
4
+ } from "./chunk-QTTJRK4J.js";
5
+ import "./chunk-JDPG7F4Z.js";
6
+ export {
7
+ run
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}