@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.
- package/README.md +62 -0
- package/dist/bin.js +18 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-JDPG7F4Z.js +688 -0
- package/dist/chunk-JDPG7F4Z.js.map +1 -0
- package/dist/chunk-QTTJRK4J.js +156 -0
- package/dist/chunk-QTTJRK4J.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/serve-OI2RGYVP.js +46873 -0
- package/dist/serve-OI2RGYVP.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
14
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
30
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
|
+
mod
|
|
32
|
+
));
|
|
33
|
+
|
|
34
|
+
// ../mcp-runtime/auth/token-store.ts
|
|
35
|
+
import fs from "fs";
|
|
36
|
+
import fsp from "fs/promises";
|
|
37
|
+
import path2 from "path";
|
|
38
|
+
|
|
39
|
+
// ../mcp-runtime/paths.ts
|
|
40
|
+
import os from "os";
|
|
41
|
+
import path from "path";
|
|
42
|
+
function tempoHome() {
|
|
43
|
+
return process.env.TEMPO_MCP_HOME ?? path.join(os.homedir(), ".tempo");
|
|
44
|
+
}
|
|
45
|
+
function authFilePath() {
|
|
46
|
+
return path.join(tempoHome(), "auth.json");
|
|
47
|
+
}
|
|
48
|
+
function logsDir() {
|
|
49
|
+
return path.join(tempoHome(), "logs");
|
|
50
|
+
}
|
|
51
|
+
function logFilePath(binaryName) {
|
|
52
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
53
|
+
return path.join(logsDir(), `mcp-${binaryName}-${date}.log`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ../mcp-runtime/auth/token-store.ts
|
|
57
|
+
async function readAuth() {
|
|
58
|
+
try {
|
|
59
|
+
const raw = await fsp.readFile(authFilePath(), "utf8");
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (typeof parsed.token === "string" && typeof parsed.sessionId === "string" && typeof parsed.userId === "string" && typeof parsed.expiresAt === "number") {
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.code === "ENOENT") return null;
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function readAuthSync() {
|
|
71
|
+
try {
|
|
72
|
+
const raw = fs.readFileSync(authFilePath(), "utf8");
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
if (typeof parsed.token === "string" && typeof parsed.sessionId === "string" && typeof parsed.userId === "string" && typeof parsed.expiresAt === "number") {
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function writeAuth(auth) {
|
|
83
|
+
const dir = tempoHome();
|
|
84
|
+
await fsp.mkdir(dir, { recursive: true, mode: 448 });
|
|
85
|
+
const target = authFilePath();
|
|
86
|
+
const tmp = path2.join(dir, `auth.json.tmp.${process.pid}.${Date.now()}`);
|
|
87
|
+
const data = JSON.stringify(auth, null, 2);
|
|
88
|
+
await fsp.writeFile(tmp, data, { mode: 384 });
|
|
89
|
+
await fsp.rename(tmp, target);
|
|
90
|
+
try {
|
|
91
|
+
await fsp.chmod(target, 384);
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function clearAuth() {
|
|
96
|
+
try {
|
|
97
|
+
await fsp.unlink(authFilePath());
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (err.code !== "ENOENT") throw err;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ../mcp-runtime/auth/refresh-loop.ts
|
|
104
|
+
function createRefreshLoop(opts) {
|
|
105
|
+
const leadMs = opts.refreshLeadMs ?? 6e4;
|
|
106
|
+
const retryDelay = opts.retryDelayMs ?? 5e3;
|
|
107
|
+
const maxRetries = opts.maxRetries ?? 3;
|
|
108
|
+
let timer = null;
|
|
109
|
+
let stopped = false;
|
|
110
|
+
async function refreshNow() {
|
|
111
|
+
const current = await readAuth();
|
|
112
|
+
if (!current) {
|
|
113
|
+
opts.onReauthRequired();
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
117
|
+
try {
|
|
118
|
+
const res = await fetch(`${opts.convexSiteUrl}/auth/refresh`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: { "Content-Type": "application/json" },
|
|
121
|
+
body: JSON.stringify({ sessionId: current.sessionId })
|
|
122
|
+
});
|
|
123
|
+
if (res.status === 401) {
|
|
124
|
+
await clearAuth();
|
|
125
|
+
opts.onReauthRequired();
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (!res.ok) {
|
|
129
|
+
if (attempt < maxRetries) {
|
|
130
|
+
await sleep(retryDelay * Math.pow(1.5, attempt));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
opts.onReauthRequired();
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const body = await res.json();
|
|
137
|
+
if (!body.token) {
|
|
138
|
+
opts.onReauthRequired();
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const next = {
|
|
142
|
+
...current,
|
|
143
|
+
token: body.token,
|
|
144
|
+
expiresAt: decodeJwtExp(body.token)
|
|
145
|
+
};
|
|
146
|
+
await writeAuth(next);
|
|
147
|
+
return body.token;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
if (attempt < maxRetries) {
|
|
150
|
+
await sleep(retryDelay * Math.pow(1.5, attempt));
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
opts.onReauthRequired();
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function schedule(auth) {
|
|
160
|
+
if (timer) clearTimeout(timer);
|
|
161
|
+
if (stopped) return;
|
|
162
|
+
const delay = Math.max(0, auth.expiresAt - Date.now() - leadMs);
|
|
163
|
+
timer = setTimeout(() => {
|
|
164
|
+
void refreshNow().then((newToken) => {
|
|
165
|
+
if (newToken && !stopped) {
|
|
166
|
+
void readAuth().then((a) => {
|
|
167
|
+
if (a) schedule(a);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}, delay);
|
|
172
|
+
}
|
|
173
|
+
function start() {
|
|
174
|
+
stopped = false;
|
|
175
|
+
void readAuth().then((auth) => {
|
|
176
|
+
if (auth) schedule(auth);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function stop() {
|
|
180
|
+
stopped = true;
|
|
181
|
+
if (timer) {
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
timer = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { start, stop, refreshNow };
|
|
187
|
+
}
|
|
188
|
+
function sleep(ms) {
|
|
189
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
190
|
+
}
|
|
191
|
+
function decodeJwtExp(jwt) {
|
|
192
|
+
try {
|
|
193
|
+
const [, payload] = jwt.split(".");
|
|
194
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
195
|
+
if (typeof decoded.exp === "number") return decoded.exp * 1e3;
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
return Date.now() + 5 * 6e4;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ../mcp-runtime/auth/auth-provider.ts
|
|
202
|
+
function createAuthProvider(refreshLoop) {
|
|
203
|
+
return {
|
|
204
|
+
async getToken() {
|
|
205
|
+
const auth = await readAuth();
|
|
206
|
+
if (!auth) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
"Not authenticated. Run `tempo-mcp login` (or sign in via Tempo)."
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (auth.expiresAt - Date.now() < 3e4) {
|
|
212
|
+
const newToken = await refreshLoop.refreshNow();
|
|
213
|
+
if (newToken) {
|
|
214
|
+
const fresh = await readAuth();
|
|
215
|
+
if (fresh) {
|
|
216
|
+
return { jwt: fresh.token, expiresAt: fresh.expiresAt };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
throw new Error(
|
|
220
|
+
"Tempo session expired. Run `tempo-mcp login` to sign in again."
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return { jwt: auth.token, expiresAt: auth.expiresAt };
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ../mcp-runtime/auth/browser-flow.ts
|
|
229
|
+
import http from "http";
|
|
230
|
+
async function startBrowserAuthFlow(opts = {}) {
|
|
231
|
+
const authUrl = opts.authUrl ?? "https://auth.tempo.build";
|
|
232
|
+
const timeoutMs = opts.timeoutMs ?? 10 * 6e4;
|
|
233
|
+
const bindAddress = opts.bindAddress ?? "127.0.0.1";
|
|
234
|
+
let resolveDone;
|
|
235
|
+
let rejectDone;
|
|
236
|
+
const done = new Promise((resolve, reject) => {
|
|
237
|
+
resolveDone = resolve;
|
|
238
|
+
rejectDone = reject;
|
|
239
|
+
});
|
|
240
|
+
const server = http.createServer(async (req, res) => {
|
|
241
|
+
if (req.method === "POST" && req.url && req.url.startsWith("/tempo/auth")) {
|
|
242
|
+
try {
|
|
243
|
+
const body = await readJson(req);
|
|
244
|
+
if (!body || typeof body.token !== "string" || typeof body.sessionId !== "string" || typeof body.userId !== "string") {
|
|
245
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
246
|
+
res.end(JSON.stringify({ error: "missing fields" }));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
await writeAuth({
|
|
250
|
+
token: body.token,
|
|
251
|
+
sessionId: body.sessionId,
|
|
252
|
+
userId: body.userId,
|
|
253
|
+
email: typeof body.email === "string" ? body.email : void 0,
|
|
254
|
+
firstName: typeof body.firstName === "string" ? body.firstName : void 0,
|
|
255
|
+
lastName: typeof body.lastName === "string" ? body.lastName : void 0,
|
|
256
|
+
expiresAt: decodeJwtExp(body.token),
|
|
257
|
+
lastOrgId: typeof body.orgId === "string" ? body.orgId : null
|
|
258
|
+
});
|
|
259
|
+
res.writeHead(200, {
|
|
260
|
+
"Content-Type": "application/json",
|
|
261
|
+
"Access-Control-Allow-Origin": "*"
|
|
262
|
+
});
|
|
263
|
+
res.end(JSON.stringify({ ok: true }));
|
|
264
|
+
setImmediate(() => {
|
|
265
|
+
server.close();
|
|
266
|
+
resolveDone();
|
|
267
|
+
});
|
|
268
|
+
} catch (err) {
|
|
269
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
270
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (req.method === "OPTIONS") {
|
|
275
|
+
res.writeHead(204, {
|
|
276
|
+
"Access-Control-Allow-Origin": "*",
|
|
277
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
278
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
279
|
+
});
|
|
280
|
+
res.end();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
res.writeHead(404);
|
|
284
|
+
res.end();
|
|
285
|
+
});
|
|
286
|
+
await new Promise((resolve, reject) => {
|
|
287
|
+
server.once("error", reject);
|
|
288
|
+
server.listen(opts.port ?? 0, bindAddress, () => resolve());
|
|
289
|
+
});
|
|
290
|
+
const address = server.address();
|
|
291
|
+
if (!address || typeof address === "string") {
|
|
292
|
+
server.close();
|
|
293
|
+
throw new Error("Failed to bind callback listener");
|
|
294
|
+
}
|
|
295
|
+
const callbackPort = address.port;
|
|
296
|
+
const signInUrl = `${authUrl}/sign-in?callbackPort=${callbackPort}`;
|
|
297
|
+
const timeoutTimer = setTimeout(() => {
|
|
298
|
+
server.close();
|
|
299
|
+
rejectDone(new Error(`Sign-in timeout after ${timeoutMs}ms`));
|
|
300
|
+
}, timeoutMs);
|
|
301
|
+
if (typeof timeoutTimer.unref === "function") timeoutTimer.unref();
|
|
302
|
+
done.finally(() => clearTimeout(timeoutTimer)).catch(() => {
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
signInUrl,
|
|
306
|
+
callbackPort,
|
|
307
|
+
done,
|
|
308
|
+
cancel: () => {
|
|
309
|
+
server.close();
|
|
310
|
+
rejectDone(new Error("Sign-in cancelled"));
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function readJson(req) {
|
|
315
|
+
return new Promise((resolve, reject) => {
|
|
316
|
+
let raw = "";
|
|
317
|
+
req.setEncoding("utf8");
|
|
318
|
+
req.on("data", (chunk) => {
|
|
319
|
+
raw += chunk;
|
|
320
|
+
if (raw.length > 1e6) {
|
|
321
|
+
req.destroy();
|
|
322
|
+
reject(new Error("payload too large"));
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
req.on("end", () => {
|
|
326
|
+
if (!raw) return resolve(null);
|
|
327
|
+
try {
|
|
328
|
+
resolve(JSON.parse(raw));
|
|
329
|
+
} catch (err) {
|
|
330
|
+
reject(err);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
req.on("error", reject);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ../mcp-runtime/aggregate/aggregator.ts
|
|
338
|
+
import { randomUUID } from "crypto";
|
|
339
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
340
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
341
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
342
|
+
import {
|
|
343
|
+
CallToolRequestSchema,
|
|
344
|
+
ListResourcesRequestSchema,
|
|
345
|
+
ListToolsRequestSchema,
|
|
346
|
+
ReadResourceRequestSchema
|
|
347
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
348
|
+
var BACKEND_CALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
349
|
+
async function createAggregateServer(options) {
|
|
350
|
+
const {
|
|
351
|
+
name,
|
|
352
|
+
version,
|
|
353
|
+
instructions,
|
|
354
|
+
backends,
|
|
355
|
+
filterTool,
|
|
356
|
+
injectParams,
|
|
357
|
+
onScopedCall
|
|
358
|
+
} = options;
|
|
359
|
+
const connected = [];
|
|
360
|
+
for (const backend of backends) {
|
|
361
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
362
|
+
const client = new Client({
|
|
363
|
+
name: `${name}-aggregator`,
|
|
364
|
+
version
|
|
365
|
+
});
|
|
366
|
+
await backend.server.connect(serverTransport);
|
|
367
|
+
await client.connect(clientTransport);
|
|
368
|
+
connected.push({ ...backend, client });
|
|
369
|
+
}
|
|
370
|
+
const toolRoutes = /* @__PURE__ */ new Map();
|
|
371
|
+
const injectedByTool = /* @__PURE__ */ new Map();
|
|
372
|
+
const toolsByBackend = /* @__PURE__ */ new Map();
|
|
373
|
+
for (const backend of connected) {
|
|
374
|
+
if (!backend.client.getServerCapabilities()?.tools) {
|
|
375
|
+
toolsByBackend.set(backend.toolset, []);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const listed = await backend.client.listTools();
|
|
379
|
+
const kept = [];
|
|
380
|
+
for (const tool of listed.tools) {
|
|
381
|
+
if (filterTool && !filterTool(backend.toolset, tool.name)) continue;
|
|
382
|
+
const existing = toolRoutes.get(tool.name);
|
|
383
|
+
if (existing) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`MCP aggregate tool name collision: "${tool.name}" registered by both "${existing.toolset}" and "${backend.toolset}"`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
toolRoutes.set(tool.name, backend);
|
|
389
|
+
const injected = injectParams?.(backend.toolset, tool.name) ?? [];
|
|
390
|
+
if (injected.length === 0) {
|
|
391
|
+
kept.push(tool);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
injectedByTool.set(tool.name, injected);
|
|
395
|
+
const schema = tool.inputSchema ?? { type: "object" };
|
|
396
|
+
kept.push({
|
|
397
|
+
...tool,
|
|
398
|
+
inputSchema: {
|
|
399
|
+
...schema,
|
|
400
|
+
type: "object",
|
|
401
|
+
properties: {
|
|
402
|
+
...schema.properties ?? {},
|
|
403
|
+
...Object.fromEntries(
|
|
404
|
+
injected.map((param) => [
|
|
405
|
+
param.name,
|
|
406
|
+
{ type: "string", description: param.description }
|
|
407
|
+
])
|
|
408
|
+
)
|
|
409
|
+
},
|
|
410
|
+
required: [
|
|
411
|
+
.../* @__PURE__ */ new Set([
|
|
412
|
+
...schema.required ?? [],
|
|
413
|
+
...injected.map((param) => param.name)
|
|
414
|
+
])
|
|
415
|
+
]
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
toolsByBackend.set(backend.toolset, kept);
|
|
420
|
+
}
|
|
421
|
+
const server = new Server(
|
|
422
|
+
{ name, version },
|
|
423
|
+
{
|
|
424
|
+
capabilities: { tools: {}, resources: {} },
|
|
425
|
+
instructions
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
429
|
+
tools: connected.flatMap(
|
|
430
|
+
(backend) => toolsByBackend.get(backend.toolset) ?? []
|
|
431
|
+
)
|
|
432
|
+
}));
|
|
433
|
+
let callChain = Promise.resolve();
|
|
434
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
435
|
+
const toolName = request.params.name;
|
|
436
|
+
const backend = toolRoutes.get(toolName);
|
|
437
|
+
if (!backend) {
|
|
438
|
+
return {
|
|
439
|
+
isError: true,
|
|
440
|
+
content: [
|
|
441
|
+
{
|
|
442
|
+
type: "text",
|
|
443
|
+
text: JSON.stringify({
|
|
444
|
+
error: {
|
|
445
|
+
code: "unknown_tool",
|
|
446
|
+
message: `No registered tool named "${toolName}"`
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const args = {
|
|
454
|
+
...request.params.arguments ?? {}
|
|
455
|
+
};
|
|
456
|
+
const injected = injectedByTool.get(toolName) ?? [];
|
|
457
|
+
const scopeValues = {};
|
|
458
|
+
for (const param of injected) {
|
|
459
|
+
const value = args[param.name];
|
|
460
|
+
delete args[param.name];
|
|
461
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
462
|
+
return {
|
|
463
|
+
isError: true,
|
|
464
|
+
content: [
|
|
465
|
+
{
|
|
466
|
+
type: "text",
|
|
467
|
+
text: JSON.stringify({
|
|
468
|
+
error: {
|
|
469
|
+
code: "missing_scope_param",
|
|
470
|
+
message: `"${toolName}" requires "${param.name}". ${param.description}`
|
|
471
|
+
}
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
]
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
scopeValues[param.name] = value;
|
|
478
|
+
}
|
|
479
|
+
const run = callChain.then(async () => {
|
|
480
|
+
if (injected.length > 0) onScopedCall?.(scopeValues);
|
|
481
|
+
const requestId = randomUUID();
|
|
482
|
+
backend.onRequestStart?.(requestId);
|
|
483
|
+
try {
|
|
484
|
+
return await backend.client.callTool(
|
|
485
|
+
{ name: toolName, arguments: args },
|
|
486
|
+
void 0,
|
|
487
|
+
{ timeout: BACKEND_CALL_TIMEOUT_MS }
|
|
488
|
+
);
|
|
489
|
+
} finally {
|
|
490
|
+
backend.onRequestEnd?.(requestId);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
callChain = run.then(
|
|
494
|
+
() => void 0,
|
|
495
|
+
() => void 0
|
|
496
|
+
);
|
|
497
|
+
return run;
|
|
498
|
+
});
|
|
499
|
+
const listResourcesForBackend = async (backend) => {
|
|
500
|
+
const caps = backend.client.getServerCapabilities();
|
|
501
|
+
if (!caps?.resources) return [];
|
|
502
|
+
const listed = await backend.client.listResources();
|
|
503
|
+
return listed.resources;
|
|
504
|
+
};
|
|
505
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
506
|
+
const all = await Promise.all(connected.map(listResourcesForBackend));
|
|
507
|
+
return { resources: all.flat() };
|
|
508
|
+
});
|
|
509
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
510
|
+
const uri = request.params.uri;
|
|
511
|
+
for (const backend of connected) {
|
|
512
|
+
const resources = await listResourcesForBackend(backend);
|
|
513
|
+
if (resources.some((resource) => resource.uri === uri)) {
|
|
514
|
+
return backend.client.readResource({ uri });
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
518
|
+
});
|
|
519
|
+
return {
|
|
520
|
+
server,
|
|
521
|
+
connect: (transport) => server.connect(transport),
|
|
522
|
+
dispose: async () => {
|
|
523
|
+
for (const backend of connected) {
|
|
524
|
+
await backend.client.close().catch(() => {
|
|
525
|
+
});
|
|
526
|
+
backend.dispose?.();
|
|
527
|
+
}
|
|
528
|
+
await server.close().catch(() => {
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ../mcp-runtime/safety/process-handlers.ts
|
|
535
|
+
import fs2 from "fs";
|
|
536
|
+
import fsp2 from "fs/promises";
|
|
537
|
+
import path3 from "path";
|
|
538
|
+
function installProcessSafety(opts = {}) {
|
|
539
|
+
const binaryName = opts.binaryName ?? path3.basename(process.argv[1] ?? "tempo-mcp");
|
|
540
|
+
const logError = (label, err) => {
|
|
541
|
+
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${label}: ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
542
|
+
`;
|
|
543
|
+
try {
|
|
544
|
+
fs2.mkdirSync(logsDir(), { recursive: true, mode: 448 });
|
|
545
|
+
fs2.appendFileSync(logFilePath(binaryName), line, { mode: 384 });
|
|
546
|
+
} catch {
|
|
547
|
+
try {
|
|
548
|
+
process.stderr.write(line);
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
const onUncaught = (err) => logError("uncaughtException", err);
|
|
554
|
+
const onUnhandled = (reason) => logError("unhandledRejection", reason);
|
|
555
|
+
process.on("uncaughtException", onUncaught);
|
|
556
|
+
process.on("unhandledRejection", onUnhandled);
|
|
557
|
+
const stdoutError = (err) => {
|
|
558
|
+
if (err.code !== "EPIPE") logError("stdout-error", err);
|
|
559
|
+
};
|
|
560
|
+
process.stdout.on("error", stdoutError);
|
|
561
|
+
const onSignal = (signal) => {
|
|
562
|
+
Promise.resolve(opts.onShutdown?.()).catch((err) => logError(`shutdown:${signal}`, err)).finally(() => {
|
|
563
|
+
setTimeout(() => process.exit(0), 50);
|
|
564
|
+
});
|
|
565
|
+
};
|
|
566
|
+
process.once("SIGTERM", () => onSignal("SIGTERM"));
|
|
567
|
+
process.once("SIGINT", () => onSignal("SIGINT"));
|
|
568
|
+
return () => {
|
|
569
|
+
process.off("uncaughtException", onUncaught);
|
|
570
|
+
process.off("unhandledRejection", onUnhandled);
|
|
571
|
+
process.stdout.off("error", stdoutError);
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// ../mcp-runtime/elicitation/auth-elicitation.ts
|
|
576
|
+
async function requestAuthElicitation(opts = {}) {
|
|
577
|
+
const handle = await startBrowserAuthFlow({
|
|
578
|
+
authUrl: opts.authUrl,
|
|
579
|
+
timeoutMs: opts.timeoutMs ?? 5 * 6e4
|
|
580
|
+
});
|
|
581
|
+
return {
|
|
582
|
+
signInUrl: handle.signInUrl,
|
|
583
|
+
waitForSignIn: () => handle.done,
|
|
584
|
+
cancel: () => handle.cancel()
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
async function ensureAuth(opts = {}) {
|
|
588
|
+
const existing = await readAuth();
|
|
589
|
+
if (existing && existing.expiresAt - Date.now() > 6e4) {
|
|
590
|
+
return { hadExistingAuth: true };
|
|
591
|
+
}
|
|
592
|
+
const eli = await requestAuthElicitation(opts);
|
|
593
|
+
await eli.waitForSignIn();
|
|
594
|
+
return { hadExistingAuth: false, signInUrl: eli.signInUrl };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ../mcp-runtime/transport/pin-interceptor.ts
|
|
598
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
599
|
+
|
|
600
|
+
// src/config.ts
|
|
601
|
+
import fs3 from "fs";
|
|
602
|
+
import path4 from "path";
|
|
603
|
+
var DEFAULT_CONVEX_URL = "https://greedy-jackal-526.convex.cloud";
|
|
604
|
+
function resolveConvexUrl() {
|
|
605
|
+
return process.env.TEMPO_CONVEX_URL ?? DEFAULT_CONVEX_URL;
|
|
606
|
+
}
|
|
607
|
+
function resolveConvexSiteUrl(convexUrl) {
|
|
608
|
+
const explicit = process.env.TEMPO_CONVEX_SITE_URL;
|
|
609
|
+
if (explicit) return explicit;
|
|
610
|
+
return convexUrl.replace(/\.convex\.cloud$/, ".convex.site");
|
|
611
|
+
}
|
|
612
|
+
var ALL_TOOLSETS = [
|
|
613
|
+
"issues",
|
|
614
|
+
"docs",
|
|
615
|
+
"comments",
|
|
616
|
+
"agents",
|
|
617
|
+
"scripts",
|
|
618
|
+
"slack",
|
|
619
|
+
"linear",
|
|
620
|
+
"canvas"
|
|
621
|
+
];
|
|
622
|
+
function parseServeArgs(args) {
|
|
623
|
+
let toolsets = [...ALL_TOOLSETS];
|
|
624
|
+
let readonly = process.env.TEMPO_MCP_READONLY === "1";
|
|
625
|
+
for (let i = 0; i < args.length; i++) {
|
|
626
|
+
const arg = args[i];
|
|
627
|
+
if (arg === "--readonly") {
|
|
628
|
+
readonly = true;
|
|
629
|
+
} else if (arg === "--toolsets") {
|
|
630
|
+
const value = args[++i];
|
|
631
|
+
if (!value) return { error: "--toolsets requires a comma-separated list" };
|
|
632
|
+
const requested = value.split(",").map((t) => t.trim()).filter(Boolean);
|
|
633
|
+
const unknown = requested.filter(
|
|
634
|
+
(t) => !ALL_TOOLSETS.includes(t)
|
|
635
|
+
);
|
|
636
|
+
if (unknown.length) {
|
|
637
|
+
return {
|
|
638
|
+
error: `Unknown toolset(s): ${unknown.join(", ")}. Valid: ${ALL_TOOLSETS.join(", ")}`
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
toolsets = requested;
|
|
642
|
+
} else {
|
|
643
|
+
return { error: `Unknown flag: ${arg}` };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return { toolsets, readonly };
|
|
647
|
+
}
|
|
648
|
+
function discoverWorkspace(startDir = process.cwd()) {
|
|
649
|
+
let dir = path4.resolve(startDir);
|
|
650
|
+
for (; ; ) {
|
|
651
|
+
const configPath = path4.join(dir, "tempo", "tempo.config.json");
|
|
652
|
+
if (fs3.existsSync(configPath)) {
|
|
653
|
+
let canvasesRel = "./designs";
|
|
654
|
+
try {
|
|
655
|
+
const parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
656
|
+
if (parsed.paths?.canvases) canvasesRel = parsed.paths.canvases;
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
workspaceRoot: dir,
|
|
661
|
+
canvasesDir: path4.resolve(dir, "tempo", canvasesRel)
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
const parent = path4.dirname(dir);
|
|
665
|
+
if (parent === dir) return null;
|
|
666
|
+
dir = parent;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export {
|
|
671
|
+
__require,
|
|
672
|
+
__commonJS,
|
|
673
|
+
__toESM,
|
|
674
|
+
readAuth,
|
|
675
|
+
readAuthSync,
|
|
676
|
+
clearAuth,
|
|
677
|
+
createRefreshLoop,
|
|
678
|
+
createAuthProvider,
|
|
679
|
+
startBrowserAuthFlow,
|
|
680
|
+
createAggregateServer,
|
|
681
|
+
installProcessSafety,
|
|
682
|
+
ensureAuth,
|
|
683
|
+
resolveConvexUrl,
|
|
684
|
+
resolveConvexSiteUrl,
|
|
685
|
+
parseServeArgs,
|
|
686
|
+
discoverWorkspace
|
|
687
|
+
};
|
|
688
|
+
//# sourceMappingURL=chunk-JDPG7F4Z.js.map
|