@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 +2 -2
- package/dist/call-6CDIHKDB.js +130 -0
- package/dist/call-6CDIHKDB.js.map +1 -0
- package/dist/{chunk-EYHFTWP5.js → chunk-6HT4KAGI.js} +47 -5
- package/dist/chunk-6HT4KAGI.js.map +1 -0
- package/dist/{chunk-JDPG7F4Z.js → chunk-J43EDR63.js} +252 -17
- package/dist/chunk-J43EDR63.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/{serve-ODDE266E.js → serve-JG5U3XF4.js} +80 -110
- package/dist/serve-JG5U3XF4.js.map +1 -0
- package/dist/token-VESH5QAF.js +136 -0
- package/dist/token-VESH5QAF.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-EYHFTWP5.js.map +0 -1
- package/dist/chunk-JDPG7F4Z.js.map +0 -1
- package/dist/serve-ODDE266E.js.map +0 -1
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-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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
return null;
|
|
144
|
+
return reauthOrGiveUp();
|
|
135
145
|
}
|
|
136
146
|
const body = await res.json();
|
|
137
147
|
if (!body.token) {
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
923
|
+
//# sourceMappingURL=chunk-J43EDR63.js.map
|