@tempo-ai/mcp 0.0.100 → 0.0.101

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 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-EYHFTWP5.js";
6
- import "./chunk-JDPG7F4Z.js";
5
+ } from "./chunk-6HT4KAGI.js";
6
+ import "./chunk-J43EDR63.js";
7
7
 
8
8
  // src/bin.ts
9
9
  run(process.argv).then(
@@ -0,0 +1,130 @@
1
+ import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
2
+ import {
3
+ ensureCliAuth,
4
+ parseServeArgs
5
+ } from "./chunk-J43EDR63.js";
6
+
7
+ // src/call.ts
8
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
10
+ async function connectAggregate(options) {
11
+ const { createTempoAggregate } = await import("./serve-JG5U3XF4.js");
12
+ const handle = await createTempoAggregate(options);
13
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
14
+ const client = new Client({ name: "tempo-mcp-cli", version: "0" });
15
+ await handle.aggregate.connect(serverTransport);
16
+ await client.connect(clientTransport);
17
+ return {
18
+ client,
19
+ dispose: async () => {
20
+ await client.close();
21
+ await handle.dispose();
22
+ }
23
+ };
24
+ }
25
+ function extractFlag(args, flag) {
26
+ const rest = args.filter((a) => a !== flag);
27
+ return { present: rest.length !== args.length, rest };
28
+ }
29
+ async function runTools(args) {
30
+ const { present: withSchemas, rest } = extractFlag(args, "--schemas");
31
+ const options = parseServeArgs(rest);
32
+ if ("error" in options) {
33
+ console.error(options.error);
34
+ return 1;
35
+ }
36
+ await ensureCliAuth();
37
+ const { client, dispose } = await connectAggregate(options);
38
+ try {
39
+ const { tools } = await client.listTools();
40
+ const rows = tools.map(
41
+ (tool) => withSchemas ? {
42
+ name: tool.name,
43
+ description: tool.description,
44
+ inputSchema: tool.inputSchema
45
+ } : { name: tool.name, description: tool.description }
46
+ );
47
+ console.log(JSON.stringify({ tools: rows }, null, 2));
48
+ return 0;
49
+ } finally {
50
+ await dispose();
51
+ }
52
+ }
53
+ async function runCall(args) {
54
+ const [toolName, ...rest] = args;
55
+ if (!toolName || toolName.startsWith("--")) {
56
+ console.error(
57
+ `Usage: tempo-mcp call <tool> --json '{"orgId": "...", ...}'
58
+ Discover tools and their schemas with \`tempo-mcp tools --schemas\`.`
59
+ );
60
+ return 1;
61
+ }
62
+ let jsonArg = null;
63
+ const serveFlags = [];
64
+ for (let i = 0; i < rest.length; i++) {
65
+ if (rest[i] === "--json") {
66
+ jsonArg = rest[++i] ?? null;
67
+ } else {
68
+ serveFlags.push(rest[i]);
69
+ }
70
+ }
71
+ let toolArgs = {};
72
+ if (jsonArg != null) {
73
+ if (jsonArg === "-") {
74
+ jsonArg = await readStdin();
75
+ }
76
+ try {
77
+ const parsed = JSON.parse(jsonArg);
78
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
79
+ console.error("--json must be a JSON object of tool arguments");
80
+ return 1;
81
+ }
82
+ toolArgs = parsed;
83
+ } catch (err) {
84
+ console.error(
85
+ `--json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
86
+ );
87
+ return 1;
88
+ }
89
+ }
90
+ const options = parseServeArgs(serveFlags);
91
+ if ("error" in options) {
92
+ console.error(options.error);
93
+ return 1;
94
+ }
95
+ await ensureCliAuth();
96
+ const { client, dispose } = await connectAggregate(options);
97
+ try {
98
+ const result = await client.callTool({
99
+ name: toolName,
100
+ arguments: toolArgs
101
+ });
102
+ const content = Array.isArray(result.content) ? result.content : [];
103
+ for (const block of content) {
104
+ if (block.type === "text") {
105
+ console.log(block.text);
106
+ } else {
107
+ console.log(JSON.stringify(block));
108
+ }
109
+ }
110
+ return result.isError ? 1 : 0;
111
+ } finally {
112
+ await dispose();
113
+ }
114
+ }
115
+ function readStdin() {
116
+ return new Promise((resolve, reject) => {
117
+ const chunks = [];
118
+ process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
119
+ process.stdin.on(
120
+ "end",
121
+ () => resolve(Buffer.concat(chunks).toString("utf8"))
122
+ );
123
+ process.stdin.on("error", reject);
124
+ });
125
+ }
126
+ export {
127
+ runCall,
128
+ runTools
129
+ };
130
+ //# sourceMappingURL=call-6CDIHKDB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/call.ts"],"sourcesContent":["/**\n * `tempo-mcp tools` and `tempo-mcp call` — the universal CLI surface\n * (TEM-2121) for hosts that cannot attach an MCP server at all (Codex\n * cloud has no MCP support; Grok Bot attaches remote-URL servers only).\n * Any agent with a terminal drives the EXACT same pipeline the MCP server\n * exposes — same aggregator, explicit orgId/projectId scoping, manifest\n * gating, canvas hooks — through two generic commands:\n *\n * tempo-mcp tools [--schemas] [--toolsets a,b] [--readonly]\n * tempo-mcp call <tool> --json '{\"orgId\": \"...\", ...}'\n *\n * An in-memory MCP client is connected to the same aggregate `runServe`\n * would put on stdio, so there is no per-tool code and no second code\n * path to drift.\n */\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport { parseServeArgs, type ServeOptions } from \"./config.ts\";\nimport { ensureCliAuth } from \"./cli-auth.ts\";\n\ninterface ConnectedAggregate {\n client: Client;\n dispose: () => Promise<void>;\n}\n\nasync function connectAggregate(\n options: ServeOptions,\n): Promise<ConnectedAggregate> {\n const { createTempoAggregate } = await import(\"./serve.ts\");\n const handle = await createTempoAggregate(options);\n const [clientTransport, serverTransport] =\n InMemoryTransport.createLinkedPair();\n const client = new Client({ name: \"tempo-mcp-cli\", version: \"0\" });\n await handle.aggregate.connect(serverTransport);\n await client.connect(clientTransport);\n return {\n client,\n dispose: async () => {\n await client.close();\n await handle.dispose();\n },\n };\n}\n\n/** Split our own flags from the serve-shaping flags shared with `serve`. */\nfunction extractFlag(args: string[], flag: string): { present: boolean; rest: string[] } {\n const rest = args.filter((a) => a !== flag);\n return { present: rest.length !== args.length, rest };\n}\n\nexport async function runTools(args: string[]): Promise<number> {\n const { present: withSchemas, rest } = extractFlag(args, \"--schemas\");\n const options = parseServeArgs(rest);\n if (\"error\" in options) {\n console.error(options.error);\n return 1;\n }\n\n await ensureCliAuth();\n const { client, dispose } = await connectAggregate(options);\n try {\n const { tools } = await client.listTools();\n const rows = tools.map((tool) =>\n withSchemas\n ? {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n }\n : { name: tool.name, description: tool.description },\n );\n console.log(JSON.stringify({ tools: rows }, null, 2));\n return 0;\n } finally {\n await dispose();\n }\n}\n\nexport async function runCall(args: string[]): Promise<number> {\n const [toolName, ...rest] = args;\n if (!toolName || toolName.startsWith(\"--\")) {\n console.error(\n `Usage: tempo-mcp call <tool> --json '{\"orgId\": \"...\", ...}'\\nDiscover tools and their schemas with \\`tempo-mcp tools --schemas\\`.`,\n );\n return 1;\n }\n\n let jsonArg: string | null = null;\n const serveFlags: string[] = [];\n for (let i = 0; i < rest.length; i++) {\n if (rest[i] === \"--json\") {\n jsonArg = rest[++i] ?? null;\n } else {\n serveFlags.push(rest[i]);\n }\n }\n let toolArgs: Record<string, unknown> = {};\n if (jsonArg != null) {\n if (jsonArg === \"-\") {\n jsonArg = await readStdin();\n }\n try {\n const parsed = JSON.parse(jsonArg) as unknown;\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n console.error(\"--json must be a JSON object of tool arguments\");\n return 1;\n }\n toolArgs = parsed as Record<string, unknown>;\n } catch (err) {\n console.error(\n `--json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n return 1;\n }\n }\n\n const options = parseServeArgs(serveFlags);\n if (\"error\" in options) {\n console.error(options.error);\n return 1;\n }\n\n await ensureCliAuth();\n const { client, dispose } = await connectAggregate(options);\n try {\n const result = await client.callTool({\n name: toolName,\n arguments: toolArgs,\n });\n const content = Array.isArray(result.content) ? result.content : [];\n for (const block of content) {\n if (block.type === \"text\") {\n console.log(block.text);\n } else {\n console.log(JSON.stringify(block));\n }\n }\n return result.isError ? 1 : 0;\n } finally {\n await dispose();\n }\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on(\"data\", (chunk) => chunks.push(Buffer.from(chunk)));\n process.stdin.on(\"end\", () =>\n resolve(Buffer.concat(chunks).toString(\"utf8\")),\n );\n process.stdin.on(\"error\", reject);\n });\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,12 @@
1
1
  import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
2
2
  import {
3
3
  clearAuth,
4
+ ensureCliAuth,
5
+ envAccessToken,
4
6
  parseServeArgs,
5
7
  readAuth,
6
8
  startBrowserAuthFlow
7
- } from "./chunk-JDPG7F4Z.js";
9
+ } from "./chunk-J43EDR63.js";
8
10
 
9
11
  // src/help.ts
10
12
  var USAGE = `tempo-mcp <subcommand>
@@ -27,14 +29,30 @@ Server:
27
29
  scripts,slack,linear,canvas). Default: all.
28
30
  --readonly Register no write tools at all
29
31
 
32
+ Direct tool access (for hosts without MCP support \u2014 Codex cloud, bots):
33
+ tools [--schemas] List every tool (add --schemas for arg schemas)
34
+ call <tool> --json '{"orgId": "..."}'
35
+ Invoke any tool; same pipeline as the MCP server.
36
+ --json - reads the argument object from stdin.
37
+ Both accept --toolsets / --readonly.
38
+
30
39
  Auth (identity only):
31
40
  login Sign in via the browser (stores ~/.tempo/auth.json)
32
41
  logout Clear stored credentials
33
- whoami Show the signed-in user
42
+ whoami Show the signed-in user (exchanges TEMPO_AUTH_TOKEN
43
+ if set \u2014 the smoke test for headless environments)
44
+ token create [--name <label>]
45
+ Mint a personal access token (printed once) for
46
+ headless environments \u2014 cloud sandboxes with no
47
+ browser. Set it there as TEMPO_AUTH_TOKEN.
48
+ token list List your tokens
49
+ token revoke <id-or-prefix>
50
+ Revoke a token
34
51
 
35
52
  Environment:
36
53
  TEMPO_CONVEX_URL Override the Convex deployment (default: production)
37
54
  TEMPO_MCP_READONLY=1 Same as --readonly
55
+ TEMPO_AUTH_TOKEN Personal access token \u2014 signs in without a browser
38
56
  `;
39
57
 
40
58
  // src/login.ts
@@ -88,7 +106,19 @@ async function runLogin() {
88
106
 
89
107
  // src/whoami.ts
90
108
  async function runWhoami() {
91
- const auth = await readAuth();
109
+ let auth = await readAuth();
110
+ if (envAccessToken()) {
111
+ try {
112
+ await ensureCliAuth();
113
+ auth = await readAuth();
114
+ } catch (err) {
115
+ console.error(
116
+ "TEMPO_AUTH_TOKEN sign-in failed:",
117
+ err instanceof Error ? err.message : String(err)
118
+ );
119
+ return 1;
120
+ }
121
+ }
92
122
  if (!auth) {
93
123
  console.log("Not signed in. Run `tempo-mcp login`.");
94
124
  return 1;
@@ -127,7 +157,7 @@ async function run(argv) {
127
157
  process.stderr.write(USAGE);
128
158
  return 1;
129
159
  }
130
- const { runServe } = await import("./serve-ODDE266E.js");
160
+ const { runServe } = await import("./serve-JG5U3XF4.js");
131
161
  return await runServe(options);
132
162
  }
133
163
  switch (subcommand) {
@@ -137,6 +167,18 @@ async function run(argv) {
137
167
  return await runLogout();
138
168
  case "whoami":
139
169
  return await runWhoami();
170
+ case "token": {
171
+ const { runToken } = await import("./token-VESH5QAF.js");
172
+ return await runToken(rest);
173
+ }
174
+ case "tools": {
175
+ const { runTools } = await import("./call-6CDIHKDB.js");
176
+ return await runTools(rest);
177
+ }
178
+ case "call": {
179
+ const { runCall } = await import("./call-6CDIHKDB.js");
180
+ return await runCall(rest);
181
+ }
140
182
  default:
141
183
  console.error(`Unknown subcommand: ${subcommand}`);
142
184
  process.stderr.write(USAGE);
@@ -153,4 +195,4 @@ async function run(argv) {
153
195
  export {
154
196
  run
155
197
  };
156
- //# sourceMappingURL=chunk-EYHFTWP5.js.map
198
+ //# sourceMappingURL=chunk-6HT4KAGI.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\nDirect tool access (for hosts without MCP support — Codex cloud, bots):\n tools [--schemas] List every tool (add --schemas for arg schemas)\n call <tool> --json '{\"orgId\": \"...\"}'\n Invoke any tool; same pipeline as the MCP server.\n --json - reads the argument object from stdin.\n Both accept --toolsets / --readonly.\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 (exchanges TEMPO_AUTH_TOKEN\n if set — the smoke test for headless environments)\n token create [--name <label>]\n Mint a personal access token (printed once) for\n headless environments — cloud sandboxes with no\n browser. Set it there as TEMPO_AUTH_TOKEN.\n token list List your tokens\n token revoke <id-or-prefix>\n Revoke a token\n\nEnvironment:\n TEMPO_CONVEX_URL Override the Convex deployment (default: production)\n TEMPO_MCP_READONLY=1 Same as --readonly\n TEMPO_AUTH_TOKEN Personal access token — signs in without a browser\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, envAccessToken } from \"@tempo-modules/mcp-runtime\";\nimport { ensureCliAuth } from \"./cli-auth.ts\";\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 let auth = await readAuth();\n if (envAccessToken()) {\n // Headless environment: the env token is AUTHORITATIVE — exchange it\n // unless the cached credentials provably came from this exact token\n // (`whoami` is the standard smoke test after pasting TEMPO_AUTH_TOKEN,\n // so it must never report a stale cached identity).\n try {\n await ensureCliAuth();\n auth = await readAuth();\n } catch (err) {\n console.error(\n \"TEMPO_AUTH_TOKEN sign-in failed:\",\n err instanceof Error ? err.message : String(err),\n );\n return 1;\n }\n }\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 case \"token\": {\n const { runToken } = await import(\"./token.ts\");\n return await runToken(rest);\n }\n case \"tools\": {\n const { runTools } = await import(\"./call.ts\");\n return await runTools(rest);\n }\n case \"call\": {\n const { runCall } = await import(\"./call.ts\");\n return await runCall(rest);\n }\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":[]}
@@ -107,11 +107,23 @@ function createRefreshLoop(opts) {
107
107
  const maxRetries = opts.maxRetries ?? 3;
108
108
  let timer = null;
109
109
  let stopped = false;
110
+ async function reauthOrGiveUp() {
111
+ if (opts.attemptReauth) {
112
+ try {
113
+ if (await opts.attemptReauth()) {
114
+ const fresh = await readAuth();
115
+ if (fresh) return fresh.token;
116
+ }
117
+ } catch {
118
+ }
119
+ }
120
+ opts.onReauthRequired();
121
+ return null;
122
+ }
110
123
  async function refreshNow() {
111
124
  const current = await readAuth();
112
125
  if (!current) {
113
- opts.onReauthRequired();
114
- return null;
126
+ return reauthOrGiveUp();
115
127
  }
116
128
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
117
129
  try {
@@ -122,21 +134,18 @@ function createRefreshLoop(opts) {
122
134
  });
123
135
  if (res.status === 401) {
124
136
  await clearAuth();
125
- opts.onReauthRequired();
126
- return null;
137
+ return reauthOrGiveUp();
127
138
  }
128
139
  if (!res.ok) {
129
140
  if (attempt < maxRetries) {
130
141
  await sleep(retryDelay * Math.pow(1.5, attempt));
131
142
  continue;
132
143
  }
133
- opts.onReauthRequired();
134
- return null;
144
+ return reauthOrGiveUp();
135
145
  }
136
146
  const body = await res.json();
137
147
  if (!body.token) {
138
- opts.onReauthRequired();
139
- return null;
148
+ return reauthOrGiveUp();
140
149
  }
141
150
  const next = {
142
151
  ...current,
@@ -150,8 +159,7 @@ function createRefreshLoop(opts) {
150
159
  await sleep(retryDelay * Math.pow(1.5, attempt));
151
160
  continue;
152
161
  }
153
- opts.onReauthRequired();
154
- return null;
162
+ return reauthOrGiveUp();
155
163
  }
156
164
  }
157
165
  return null;
@@ -225,6 +233,98 @@ function createAuthProvider(refreshLoop) {
225
233
  };
226
234
  }
227
235
 
236
+ // ../mcp-runtime/auth/token-exchange.ts
237
+ import { createHash } from "crypto";
238
+ var ENV_TOKEN_VAR = "TEMPO_AUTH_TOKEN";
239
+ function envAccessToken() {
240
+ const raw = process.env[ENV_TOKEN_VAR]?.trim();
241
+ return raw ? raw : null;
242
+ }
243
+ function accessTokenFingerprint(accessToken) {
244
+ return createHash("sha256").update(accessToken).digest("hex").slice(0, 16);
245
+ }
246
+ function storedAuthMatchesAccessToken(stored, accessToken) {
247
+ return stored.accessTokenFingerprint === accessTokenFingerprint(accessToken);
248
+ }
249
+ var TokenExchangeError = class extends Error {
250
+ constructor(message, kind) {
251
+ super(message);
252
+ this.kind = kind;
253
+ this.name = "TokenExchangeError";
254
+ }
255
+ };
256
+ async function exchangeAccessToken(opts) {
257
+ const maxRetries = opts.maxRetries ?? 2;
258
+ const retryDelay = opts.retryDelayMs ?? 2e3;
259
+ let lastTransient = "exchange failed";
260
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
261
+ let res;
262
+ try {
263
+ res = await fetch(`${opts.convexSiteUrl}/auth/token-exchange`, {
264
+ method: "POST",
265
+ headers: { "Content-Type": "application/json" },
266
+ body: JSON.stringify({ token: opts.accessToken })
267
+ });
268
+ } catch (err) {
269
+ lastTransient = err instanceof Error ? err.message : String(err);
270
+ if (attempt < maxRetries) {
271
+ await sleep2(retryDelay * Math.pow(1.5, attempt));
272
+ continue;
273
+ }
274
+ break;
275
+ }
276
+ if (res.status === 401) {
277
+ throw new TokenExchangeError(
278
+ `${ENV_TOKEN_VAR} was rejected (revoked or invalid). Mint a new token with \`tempo-mcp token create\` on a signed-in machine.`,
279
+ "invalid_token"
280
+ );
281
+ }
282
+ if (!res.ok) {
283
+ lastTransient = `token exchange returned ${res.status}`;
284
+ if (attempt < maxRetries) {
285
+ await sleep2(retryDelay * Math.pow(1.5, attempt));
286
+ continue;
287
+ }
288
+ break;
289
+ }
290
+ const body = await res.json();
291
+ if (!body.token || !body.sessionId || !body.userId) {
292
+ throw new TokenExchangeError(
293
+ "token exchange returned an incomplete payload",
294
+ "transient"
295
+ );
296
+ }
297
+ const stored = {
298
+ token: body.token,
299
+ sessionId: body.sessionId,
300
+ userId: body.userId,
301
+ email: decodeJwtEmail(body.token),
302
+ expiresAt: decodeJwtExp(body.token),
303
+ accessTokenFingerprint: accessTokenFingerprint(opts.accessToken)
304
+ };
305
+ await writeAuth(stored);
306
+ return stored;
307
+ }
308
+ throw new TokenExchangeError(
309
+ `Tempo token exchange failed: ${lastTransient}`,
310
+ "transient"
311
+ );
312
+ }
313
+ function decodeJwtEmail(jwt) {
314
+ try {
315
+ const [, payload] = jwt.split(".");
316
+ const decoded = JSON.parse(
317
+ Buffer.from(payload, "base64").toString("utf8")
318
+ );
319
+ return typeof decoded.email === "string" ? decoded.email : void 0;
320
+ } catch {
321
+ return void 0;
322
+ }
323
+ }
324
+ function sleep2(ms) {
325
+ return new Promise((r) => setTimeout(r, ms));
326
+ }
327
+
228
328
  // ../mcp-runtime/auth/browser-flow.ts
229
329
  import http from "http";
230
330
  async function startBrowserAuthFlow(opts = {}) {
@@ -293,7 +393,7 @@ async function startBrowserAuthFlow(opts = {}) {
293
393
  throw new Error("Failed to bind callback listener");
294
394
  }
295
395
  const callbackPort = address.port;
296
- const signInUrl = `${authUrl}/sign-in?callbackPort=${callbackPort}`;
396
+ const signInUrl = `${authUrl}/sign-in?callbackPort=${callbackPort}&flow=mcp`;
297
397
  const timeoutTimer = setTimeout(() => {
298
398
  server.close();
299
399
  rejectDone(new Error(`Sign-in timeout after ${timeoutMs}ms`));
@@ -572,6 +672,9 @@ function installProcessSafety(opts = {}) {
572
672
  };
573
673
  }
574
674
 
675
+ // ../mcp-runtime/transport/pin-interceptor.ts
676
+ import { randomUUID as randomUUID2 } from "crypto";
677
+
575
678
  // ../mcp-runtime/elicitation/auth-elicitation.ts
576
679
  async function requestAuthElicitation(opts = {}) {
577
680
  const handle = await startBrowserAuthFlow({
@@ -594,9 +697,6 @@ async function ensureAuth(opts = {}) {
594
697
  return { hadExistingAuth: false, signInUrl: eli.signInUrl };
595
698
  }
596
699
 
597
- // ../mcp-runtime/transport/pin-interceptor.ts
598
- import { randomUUID as randomUUID2 } from "crypto";
599
-
600
700
  // src/config.ts
601
701
  import fs3 from "fs";
602
702
  import path4 from "path";
@@ -667,6 +767,136 @@ function discoverWorkspace(startDir = process.cwd()) {
667
767
  }
668
768
  }
669
769
 
770
+ // ../convex-http-client-auth/client.ts
771
+ import { ConvexHttpClient } from "convex/browser";
772
+
773
+ // ../convex-http-client-auth/errors.ts
774
+ var McpToolError = class extends Error {
775
+ constructor(error) {
776
+ super(error.message);
777
+ this.name = "McpToolError";
778
+ this.error = error;
779
+ }
780
+ };
781
+ function mapConvexError(err) {
782
+ const message = err instanceof Error ? err.message : String(err);
783
+ if (message.includes("Not authenticated") || message.includes("Unauthenticated")) {
784
+ return { code: "auth_required", message: "Authentication required. Please sign in." };
785
+ }
786
+ if (message.includes("not a member") || message.includes("Unauthorized") || message.includes("forbidden")) {
787
+ return { code: "forbidden", message: "You are not a member of this organization." };
788
+ }
789
+ if (message.includes("not found") || message.includes("Could not find")) {
790
+ return { code: "not_found", message };
791
+ }
792
+ if (message.includes("rate limit") || message.includes("Too many requests")) {
793
+ return { code: "rate_limited", message: "Rate limited. Please retry after a moment." };
794
+ }
795
+ if (message.includes("Invalid argument") || message.includes("Validator error")) {
796
+ return { code: "invalid_args", message };
797
+ }
798
+ return { code: "internal", message };
799
+ }
800
+
801
+ // ../convex-http-client-auth/client.ts
802
+ var MAX_RETRIES = 3;
803
+ var BASE_DELAY_MS = 200;
804
+ var AuthedConvexClient = class {
805
+ constructor(opts) {
806
+ this.cachedToken = null;
807
+ this.client = new ConvexHttpClient(opts.url);
808
+ this.authProvider = opts.authProvider;
809
+ }
810
+ async query(fn, args) {
811
+ return this.callWithAuth(() => this.client.query(fn, args));
812
+ }
813
+ async mutation(fn, args) {
814
+ return this.callWithAuth(() => this.client.mutation(fn, args));
815
+ }
816
+ async action(fn, args) {
817
+ return this.callWithAuth(() => this.client.action(fn, args));
818
+ }
819
+ async ensureAuth() {
820
+ const now = Date.now();
821
+ if (this.cachedToken && this.cachedToken.expiresAt - now > 3e4) {
822
+ this.client.setAuth(this.cachedToken.jwt);
823
+ return;
824
+ }
825
+ const token = await this.authProvider.getToken();
826
+ this.cachedToken = token;
827
+ this.client.setAuth(token.jwt);
828
+ }
829
+ async callWithAuth(fn) {
830
+ await this.ensureAuth();
831
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
832
+ try {
833
+ return await fn();
834
+ } catch (err) {
835
+ const mapped = mapConvexError(err);
836
+ if (mapped.code === "auth_required" && attempt === 0) {
837
+ this.cachedToken = null;
838
+ await this.ensureAuth();
839
+ continue;
840
+ }
841
+ if (mapped.code === "internal" && attempt < MAX_RETRIES - 1) {
842
+ await sleep3(BASE_DELAY_MS * 2 ** attempt);
843
+ continue;
844
+ }
845
+ throw new McpToolError(mapped);
846
+ }
847
+ }
848
+ throw new McpToolError({
849
+ code: "internal",
850
+ message: "Max retries exceeded"
851
+ });
852
+ }
853
+ };
854
+ function sleep3(ms) {
855
+ return new Promise((resolve) => setTimeout(resolve, ms));
856
+ }
857
+
858
+ // src/cli-auth.ts
859
+ async function ensureCliAuth() {
860
+ const accessToken = envAccessToken();
861
+ if (!accessToken) {
862
+ await ensureAuth();
863
+ return;
864
+ }
865
+ const existing = await readAuth();
866
+ if (existing && storedAuthMatchesAccessToken(existing, accessToken) && existing.expiresAt - Date.now() > 6e4) {
867
+ return;
868
+ }
869
+ await exchangeAccessToken({
870
+ convexSiteUrl: resolveConvexSiteUrl(resolveConvexUrl()),
871
+ accessToken
872
+ });
873
+ }
874
+ function envTokenReauth(convexSiteUrl) {
875
+ return async () => {
876
+ const accessToken = envAccessToken();
877
+ if (!accessToken) return false;
878
+ await exchangeAccessToken({ convexSiteUrl, accessToken });
879
+ return true;
880
+ };
881
+ }
882
+ function createCliConvex() {
883
+ const convexUrl = resolveConvexUrl();
884
+ const convexSiteUrl = resolveConvexSiteUrl(convexUrl);
885
+ const refreshLoop = createRefreshLoop({
886
+ convexSiteUrl,
887
+ attemptReauth: envTokenReauth(convexSiteUrl),
888
+ onReauthRequired: () => {
889
+ console.error(
890
+ "[tempo-mcp] Tempo session expired. Run `tempo-mcp login` to sign in again."
891
+ );
892
+ }
893
+ });
894
+ refreshLoop.start();
895
+ const authProvider = createAuthProvider(refreshLoop);
896
+ const convex = new AuthedConvexClient({ url: convexUrl, authProvider });
897
+ return { convex, authProvider, dispose: () => refreshLoop.stop() };
898
+ }
899
+
670
900
  export {
671
901
  __require,
672
902
  __commonJS,
@@ -676,13 +906,18 @@ export {
676
906
  clearAuth,
677
907
  createRefreshLoop,
678
908
  createAuthProvider,
909
+ envAccessToken,
679
910
  startBrowserAuthFlow,
680
911
  createAggregateServer,
681
912
  installProcessSafety,
682
- ensureAuth,
913
+ McpToolError,
914
+ AuthedConvexClient,
683
915
  resolveConvexUrl,
684
916
  resolveConvexSiteUrl,
685
917
  parseServeArgs,
686
- discoverWorkspace
918
+ discoverWorkspace,
919
+ ensureCliAuth,
920
+ envTokenReauth,
921
+ createCliConvex
687
922
  };
688
- //# sourceMappingURL=chunk-JDPG7F4Z.js.map
923
+ //# sourceMappingURL=chunk-J43EDR63.js.map