roboport 0.0.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.
Files changed (51) hide show
  1. package/README.md +25 -0
  2. package/core/agent.d.ts +35 -0
  3. package/core/index.d.ts +9 -0
  4. package/core/mcp.d.ts +6 -0
  5. package/core/message.d.ts +36 -0
  6. package/core/model.d.ts +10 -0
  7. package/core/session.d.ts +75 -0
  8. package/core/skill.d.ts +11 -0
  9. package/core/stream.d.ts +33 -0
  10. package/core/tool.d.ts +77 -0
  11. package/env.d.ts +8 -0
  12. package/harness/claudeCode.d.ts +3 -0
  13. package/harness/codex.d.ts +3 -0
  14. package/harness/core.d.ts +7 -0
  15. package/harness/index.d.ts +5 -0
  16. package/harness/index.js +1512 -0
  17. package/harness/pi.d.ts +3 -0
  18. package/harness/shared.d.ts +33 -0
  19. package/harness/tools.d.ts +33 -0
  20. package/index.d.ts +2 -0
  21. package/index.js +537 -0
  22. package/mcp/auth.d.ts +40 -0
  23. package/mcp/clients/grafana.d.ts +17 -0
  24. package/mcp/clients/linear.d.ts +9 -0
  25. package/mcp/clients/tenderly.d.ts +11 -0
  26. package/mcp/core.d.ts +30 -0
  27. package/mcp/index.d.ts +4 -0
  28. package/mcp/index.js +1356 -0
  29. package/mcp/oauth.d.ts +36 -0
  30. package/mcp/servers/calculator.d.ts +1 -0
  31. package/mcp/storage.d.ts +29 -0
  32. package/models/anthropic.d.ts +15 -0
  33. package/models/google.d.ts +14 -0
  34. package/models/index.d.ts +6 -0
  35. package/models/index.js +2039 -0
  36. package/models/moonshot.d.ts +16 -0
  37. package/models/openai-codex-auth.d.ts +17 -0
  38. package/models/openai-compatible.d.ts +41 -0
  39. package/models/openai.d.ts +29 -0
  40. package/package.json +60 -0
  41. package/skills/index.d.ts +7 -0
  42. package/skills/index.js +1007 -0
  43. package/triggers/bus.d.ts +8 -0
  44. package/triggers/core.d.ts +14 -0
  45. package/triggers/index.d.ts +7 -0
  46. package/triggers/index.js +588 -0
  47. package/triggers/sources/cron.d.ts +29 -0
  48. package/triggers/sources/github.d.ts +148 -0
  49. package/triggers/sources/grafana.d.ts +37 -0
  50. package/triggers/sources/linear.d.ts +39 -0
  51. package/triggers/sources/telegram.d.ts +85 -0
package/mcp/index.js ADDED
@@ -0,0 +1,1356 @@
1
+ // @bun
2
+ // src/core/agent.ts
3
+ import { z } from "zod";
4
+
5
+ // src/core/session.ts
6
+ class Turn {
7
+ queue = [];
8
+ waiters = [];
9
+ ended = false;
10
+ iterated = false;
11
+ resultPromise;
12
+ abortController = new AbortController;
13
+ constructor(runner) {
14
+ this.resultPromise = runner({
15
+ emit: (event) => this.emit(event),
16
+ signal: this.abortController.signal
17
+ }).then((messages) => {
18
+ this.close();
19
+ return messages;
20
+ }, (error) => {
21
+ this.close();
22
+ throw error;
23
+ });
24
+ this.resultPromise.catch(() => {});
25
+ }
26
+ emit(event) {
27
+ const waiter = this.waiters.shift();
28
+ if (waiter) {
29
+ waiter({ value: event, done: false });
30
+ } else {
31
+ this.queue.push(event);
32
+ }
33
+ }
34
+ close() {
35
+ this.ended = true;
36
+ while (this.waiters.length > 0) {
37
+ const waiter = this.waiters.shift();
38
+ waiter?.({ value: undefined, done: true });
39
+ }
40
+ }
41
+ abort(reason) {
42
+ this.abortController.abort(reason);
43
+ }
44
+ [Symbol.asyncIterator]() {
45
+ if (this.iterated) {
46
+ throw new Error("Turn can only be iterated once.");
47
+ }
48
+ this.iterated = true;
49
+ return {
50
+ next: () => {
51
+ if (this.queue.length > 0) {
52
+ const value = this.queue.shift();
53
+ return Promise.resolve({ value, done: false });
54
+ }
55
+ if (this.ended) {
56
+ return Promise.resolve({
57
+ value: undefined,
58
+ done: true
59
+ });
60
+ }
61
+ return new Promise((resolve) => this.waiters.push(resolve));
62
+ },
63
+ return: async () => {
64
+ this.abort("iteration ended");
65
+ await this.resultPromise.catch(() => {});
66
+ return {
67
+ value: undefined,
68
+ done: true
69
+ };
70
+ }
71
+ };
72
+ }
73
+ then(onfulfilled, onrejected) {
74
+ return this.resultPromise.then(onfulfilled, onrejected);
75
+ }
76
+ }
77
+
78
+ class Session {
79
+ internals;
80
+ state;
81
+ constructor(internals, state) {
82
+ this.internals = internals;
83
+ this.state = state;
84
+ }
85
+ get messages() {
86
+ return this.state.messages;
87
+ }
88
+ send(prompt) {
89
+ return this.internals.send(prompt);
90
+ }
91
+ async close() {
92
+ await this.internals.close();
93
+ }
94
+ async[Symbol.asyncDispose]() {
95
+ await this.close();
96
+ }
97
+ }
98
+
99
+ // src/core/tool.ts
100
+ import * as z4 from "zod/v4/core";
101
+ function hasParseMethod(schema) {
102
+ return typeof schema === "object" && schema !== null && "parse" in schema && typeof schema.parse === "function";
103
+ }
104
+
105
+ class Tool {
106
+ name;
107
+ description;
108
+ inputSchema;
109
+ jsonSchema;
110
+ execute;
111
+ deferred;
112
+ constructor(init) {
113
+ this.name = init.name;
114
+ this.description = init.description;
115
+ this.deferred = init.deferred ?? false;
116
+ if ("inputSchema" in init) {
117
+ this.inputSchema = init.inputSchema;
118
+ this.execute = init.execute;
119
+ } else {
120
+ this.jsonSchema = init.jsonSchema;
121
+ this.execute = init.execute;
122
+ }
123
+ }
124
+ toJsonSchema() {
125
+ if (this.jsonSchema !== undefined)
126
+ return this.jsonSchema;
127
+ if (this.inputSchema === undefined) {
128
+ throw new Error(`Tool "${this.name}" has neither inputSchema nor jsonSchema.`);
129
+ }
130
+ return z4.toJSONSchema(this.inputSchema);
131
+ }
132
+ parse(input) {
133
+ const schema = this.inputSchema;
134
+ if (!schema)
135
+ return input;
136
+ if (hasParseMethod(schema))
137
+ return schema.parse(input);
138
+ return z4.parse(schema, input);
139
+ }
140
+ }
141
+ function createRegistry(tools) {
142
+ const byName = new Map(tools.map((tool) => [tool.name, tool]));
143
+ const loadedNames = new Set(tools.filter((tool) => !tool.deferred).map((tool) => tool.name));
144
+ return {
145
+ loaded: () => tools.filter((tool) => loadedNames.has(tool.name)),
146
+ deferred: () => tools.filter((tool) => tool.deferred && !loadedNames.has(tool.name)),
147
+ load: (names) => {
148
+ const loaded = [];
149
+ const missing = [];
150
+ for (const name of names) {
151
+ const tool = byName.get(name);
152
+ if (!tool) {
153
+ missing.push(name);
154
+ continue;
155
+ }
156
+ loadedNames.add(name);
157
+ loaded.push(tool);
158
+ }
159
+ return { loaded, missing };
160
+ }
161
+ };
162
+ }
163
+
164
+ // src/core/agent.ts
165
+ class Agent {
166
+ model;
167
+ system;
168
+ tools;
169
+ skills;
170
+ mcp;
171
+ cwd;
172
+ registrations = [];
173
+ unsubs = [];
174
+ constructor({
175
+ model,
176
+ system,
177
+ tools,
178
+ skills,
179
+ mcp,
180
+ cwd
181
+ }) {
182
+ this.model = model;
183
+ this.system = system;
184
+ this.tools = tools;
185
+ this.skills = skills;
186
+ this.mcp = mcp ?? [];
187
+ this.cwd = cwd;
188
+ }
189
+ on(trigger, handler) {
190
+ this.registrations.push({ trigger, handler });
191
+ }
192
+ async start() {
193
+ for (const { trigger, handler } of this.registrations) {
194
+ const unsub = await trigger.start((event) => {
195
+ Promise.resolve().then(() => handler(event)).catch((error) => {
196
+ const message = error instanceof Error ? error.message : String(error);
197
+ console.error(`[roboport] trigger "${trigger.name}" handler failed: ${message}`);
198
+ });
199
+ });
200
+ this.unsubs.push(unsub);
201
+ }
202
+ }
203
+ async stop() {
204
+ const unsubs = this.unsubs;
205
+ this.unsubs = [];
206
+ await Promise.all(unsubs.map((u) => u()));
207
+ }
208
+ buildSystem(allTools) {
209
+ let system = this.system;
210
+ if (this.skills.length > 0) {
211
+ const skillsList = this.skills.map((skill) => `- ${skill.name}: ${skill.description}`).join(`
212
+ `);
213
+ system = `${system}
214
+
215
+ # Skills
216
+ The following skills are available. When a task matches one, call the \`Skill\` tool with that skill's name to load its full content before proceeding.
217
+
218
+ ${skillsList}`;
219
+ }
220
+ const deferred = allTools.filter((tool) => tool.deferred);
221
+ if (deferred.length > 0) {
222
+ const list = deferred.map((tool) => `- ${tool.name}`).join(`
223
+ `);
224
+ system = `${system}
225
+
226
+ # Deferred tools
227
+ These tools are available but their schemas are not loaded. Use ToolSearch to load them before calling.
228
+ ${list}`;
229
+ }
230
+ return system;
231
+ }
232
+ buildSkillTool() {
233
+ const byName = new Map(this.skills.map((skill) => [skill.name, skill]));
234
+ return new Tool({
235
+ name: "Skill",
236
+ description: 'Load the full content of a skill listed under "# Skills" in the system prompt. Call this when you decide a listed skill applies to the current task; the returned content extends your instructions for the rest of the session.',
237
+ inputSchema: z.object({
238
+ skill: z.string().describe("Name of the skill to load (must match a listed skill).")
239
+ }),
240
+ execute: ({ skill }) => {
241
+ const found = byName.get(skill);
242
+ if (!found) {
243
+ const available = [...byName.keys()].join(", ");
244
+ throw new Error(`Skill "${skill}" not found. Available: ${available}`);
245
+ }
246
+ return `<skill name="${found.name}">
247
+ ${found.content}
248
+ </skill>`;
249
+ }
250
+ });
251
+ }
252
+ session(init) {
253
+ const initialMessages = init?.messages ? [...init.messages] : [];
254
+ const sessionCwd = init?.cwd ?? this.cwd ?? process.cwd();
255
+ const state = {
256
+ messages: initialMessages,
257
+ store: new Map
258
+ };
259
+ let activeTurn = null;
260
+ let mcpConnected = false;
261
+ let allTools = null;
262
+ let registry = null;
263
+ let ctx = null;
264
+ const ensureReady = async () => {
265
+ if (!allTools || !registry || !ctx) {
266
+ const mcpToolGroups = await Promise.all(this.mcp.map((mcp) => mcp.connect()));
267
+ mcpConnected = true;
268
+ allTools = [
269
+ ...this.tools,
270
+ ...mcpToolGroups.flat(),
271
+ ...this.skills.length > 0 ? [this.buildSkillTool()] : []
272
+ ];
273
+ registry = createRegistry(allTools);
274
+ ctx = {
275
+ complete: async (p) => {
276
+ const response = await this.model.createMessage({
277
+ messages: [{ role: "user", content: p }]
278
+ });
279
+ return response.content.filter((block) => block.type === "text").map((block) => block.text).join(`
280
+ `);
281
+ },
282
+ searchWeb: (query, opts) => this.model.searchWeb(query, opts),
283
+ session: state,
284
+ tools: registry,
285
+ cwd: sessionCwd
286
+ };
287
+ if (state.messages.length === 0 || state.messages[0]?.role !== "system") {
288
+ state.messages.unshift({
289
+ role: "system",
290
+ content: this.buildSystem(allTools)
291
+ });
292
+ }
293
+ }
294
+ return { tools: allTools, registry, ctx };
295
+ };
296
+ const internals = {
297
+ send: (prompt) => {
298
+ if (activeTurn !== null) {
299
+ throw new Error("Session.send() called while another turn is in flight.");
300
+ }
301
+ const turn = new Turn(async (turnCtx) => {
302
+ try {
303
+ const ready = await ensureReady();
304
+ state.messages.push(toUserMessage(prompt));
305
+ await runAgentLoop({
306
+ model: this.model,
307
+ state,
308
+ registry: ready.registry,
309
+ ctx: ready.ctx,
310
+ emit: turnCtx.emit,
311
+ signal: turnCtx.signal
312
+ });
313
+ return [...state.messages];
314
+ } finally {
315
+ activeTurn = null;
316
+ }
317
+ });
318
+ activeTurn = turn;
319
+ return turn;
320
+ },
321
+ close: async () => {
322
+ const pending = activeTurn;
323
+ if (pending) {
324
+ pending.abort("session closed");
325
+ await Promise.resolve(pending).catch(() => {});
326
+ }
327
+ if (mcpConnected) {
328
+ await Promise.all(this.mcp.map((mcp) => mcp.disconnect()));
329
+ mcpConnected = false;
330
+ }
331
+ }
332
+ };
333
+ return new Session(internals, state);
334
+ }
335
+ }
336
+ function toUserMessage(prompt) {
337
+ if (typeof prompt === "string")
338
+ return { role: "user", content: prompt };
339
+ return { role: "user", content: prompt };
340
+ }
341
+ async function runAgentLoop({
342
+ model,
343
+ state,
344
+ registry,
345
+ ctx,
346
+ emit,
347
+ signal
348
+ }) {
349
+ while (true) {
350
+ if (signal.aborted)
351
+ break;
352
+ const active = registry.loaded();
353
+ const toolByName = new Map(active.map((tool) => [tool.name, tool]));
354
+ emit({ type: "message-start" });
355
+ const assistantContent = [];
356
+ let stopReason = "end_turn";
357
+ let usage = { inputTokens: 0, outputTokens: 0 };
358
+ try {
359
+ for await (const event of model.streamMessage({
360
+ messages: state.messages,
361
+ tools: active,
362
+ signal
363
+ })) {
364
+ switch (event.type) {
365
+ case "text-delta":
366
+ emit({ type: "text-delta", text: event.text });
367
+ break;
368
+ case "text-end":
369
+ assistantContent.push({ type: "text", text: event.text });
370
+ emit({ type: "text", text: event.text });
371
+ break;
372
+ case "thinking-delta":
373
+ emit({ type: "thinking-delta", text: event.text });
374
+ break;
375
+ case "thinking-end":
376
+ assistantContent.push({
377
+ type: "thinking",
378
+ text: event.text,
379
+ ...event.signature !== undefined ? { signature: event.signature } : {},
380
+ ...event.redactedData !== undefined ? { redactedData: event.redactedData } : {}
381
+ });
382
+ emit({
383
+ type: "thinking",
384
+ text: event.text,
385
+ ...event.signature !== undefined ? { signature: event.signature } : {}
386
+ });
387
+ break;
388
+ case "tool-call":
389
+ assistantContent.push({
390
+ type: "tool-call",
391
+ toolCallId: event.toolCallId,
392
+ toolName: event.toolName,
393
+ input: event.input
394
+ });
395
+ emit({
396
+ type: "tool-call",
397
+ toolCallId: event.toolCallId,
398
+ toolName: event.toolName,
399
+ input: event.input
400
+ });
401
+ break;
402
+ case "message-end":
403
+ stopReason = event.stopReason;
404
+ usage = event.usage;
405
+ break;
406
+ default:
407
+ break;
408
+ }
409
+ }
410
+ } catch (error) {
411
+ if (signal.aborted) {
412
+ state.messages.push({ role: "assistant", content: assistantContent });
413
+ break;
414
+ }
415
+ const err = error instanceof Error ? error : new Error(String(error));
416
+ emit({ type: "error", error: err });
417
+ throw err;
418
+ }
419
+ state.messages.push({ role: "assistant", content: assistantContent });
420
+ emit({ type: "message-end", usage });
421
+ if (stopReason !== "tool_use") {
422
+ emit({ type: "turn-end" });
423
+ break;
424
+ }
425
+ const toolCalls = assistantContent.filter((block) => block.type === "tool-call");
426
+ const results = [];
427
+ for (const call of toolCalls) {
428
+ if (signal.aborted)
429
+ break;
430
+ const tool = toolByName.get(call.toolName);
431
+ const result = await runTool(tool, call, ctx);
432
+ results.push(result);
433
+ emit({
434
+ type: "tool-result",
435
+ toolCallId: result.toolCallId,
436
+ toolName: result.toolName,
437
+ output: result.output,
438
+ isError: typeof result.output === "string" ? result.output.startsWith("Error:") : false
439
+ });
440
+ }
441
+ state.messages.push({ role: "tool", content: results });
442
+ if (signal.aborted)
443
+ break;
444
+ }
445
+ }
446
+ async function runTool(tool, call, ctx) {
447
+ if (!tool) {
448
+ return {
449
+ type: "tool-result",
450
+ toolCallId: call.toolCallId,
451
+ toolName: call.toolName,
452
+ output: `Error: tool "${call.toolName}" not found`
453
+ };
454
+ }
455
+ try {
456
+ const parsed = tool.parse(call.input);
457
+ const output = await tool.execute(parsed, ctx);
458
+ return {
459
+ type: "tool-result",
460
+ toolCallId: call.toolCallId,
461
+ toolName: call.toolName,
462
+ output
463
+ };
464
+ } catch (error) {
465
+ return {
466
+ type: "tool-result",
467
+ toolCallId: call.toolCallId,
468
+ toolName: call.toolName,
469
+ output: `Error: ${error instanceof Error ? error.message : String(error)}`
470
+ };
471
+ }
472
+ }
473
+
474
+ // src/core/model.ts
475
+ class Model {
476
+ async createMessage(params) {
477
+ const content = [];
478
+ let id = "";
479
+ let stopReason = "end_turn";
480
+ let usage = { inputTokens: 0, outputTokens: 0 };
481
+ for await (const event of this.streamMessage(params)) {
482
+ switch (event.type) {
483
+ case "text-end":
484
+ content.push({ type: "text", text: event.text });
485
+ break;
486
+ case "thinking-end":
487
+ content.push({
488
+ type: "thinking",
489
+ text: event.text,
490
+ ...event.signature !== undefined ? { signature: event.signature } : {},
491
+ ...event.redactedData !== undefined ? { redactedData: event.redactedData } : {}
492
+ });
493
+ break;
494
+ case "tool-call":
495
+ content.push({
496
+ type: "tool-call",
497
+ toolCallId: event.toolCallId,
498
+ toolName: event.toolName,
499
+ input: event.input
500
+ });
501
+ break;
502
+ case "message-end":
503
+ id = event.id;
504
+ stopReason = event.stopReason;
505
+ usage = event.usage;
506
+ break;
507
+ default:
508
+ break;
509
+ }
510
+ }
511
+ return { id, content, stopReason, usage };
512
+ }
513
+ }
514
+
515
+ // src/core/skill.ts
516
+ class Skill {
517
+ name;
518
+ description;
519
+ content;
520
+ constructor({
521
+ name,
522
+ description,
523
+ content
524
+ }) {
525
+ this.name = name;
526
+ this.description = description;
527
+ this.content = content;
528
+ }
529
+ }
530
+
531
+ // src/mcp/oauth.ts
532
+ function base64UrlEncode(bytes) {
533
+ let str = "";
534
+ for (const byte of bytes)
535
+ str += String.fromCharCode(byte);
536
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
537
+ }
538
+ function randomBytes(length) {
539
+ const bytes = new Uint8Array(length);
540
+ crypto.getRandomValues(bytes);
541
+ return bytes;
542
+ }
543
+ async function sha256(input) {
544
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
545
+ return new Uint8Array(hash);
546
+ }
547
+ async function generatePkce() {
548
+ const verifier = base64UrlEncode(randomBytes(32));
549
+ const challenge = base64UrlEncode(await sha256(verifier));
550
+ return { verifier, challenge };
551
+ }
552
+ function generateState() {
553
+ return base64UrlEncode(randomBytes(16));
554
+ }
555
+ async function discover(serverUrl) {
556
+ const u = new URL(serverUrl);
557
+ const origin = `${u.protocol}//${u.host}`;
558
+ let authServer = origin;
559
+ try {
560
+ const res2 = await fetch(`${origin}/.well-known/oauth-protected-resource`);
561
+ if (res2.ok) {
562
+ const meta = await res2.json();
563
+ if (meta.authorization_servers?.[0])
564
+ authServer = meta.authorization_servers[0];
565
+ }
566
+ } catch {}
567
+ const asUrl = new URL(authServer);
568
+ const asOrigin = `${asUrl.protocol}//${asUrl.host}`;
569
+ const res = await fetch(`${asOrigin}/.well-known/oauth-authorization-server`);
570
+ if (!res.ok) {
571
+ throw new Error(`OAuth discovery failed for ${asOrigin}: ${res.status} ${await res.text()}`);
572
+ }
573
+ return await res.json();
574
+ }
575
+ async function registerClient(registrationEndpoint, redirectUri) {
576
+ const res = await fetch(registrationEndpoint, {
577
+ method: "POST",
578
+ headers: { "content-type": "application/json" },
579
+ body: JSON.stringify({
580
+ client_name: "roboport",
581
+ redirect_uris: [redirectUri],
582
+ grant_types: ["authorization_code", "refresh_token"],
583
+ response_types: ["code"],
584
+ token_endpoint_auth_method: "none"
585
+ })
586
+ });
587
+ if (!res.ok) {
588
+ throw new Error(`OAuth client registration failed: ${res.status} ${await res.text()}`);
589
+ }
590
+ const data = await res.json();
591
+ return data.client_id;
592
+ }
593
+ function openBrowser(url) {
594
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
595
+ Bun.spawn([cmd, url], { stdout: "ignore", stderr: "ignore" });
596
+ }
597
+ function captureAuthorizationCode(port, expectedState, timeoutMs) {
598
+ return new Promise((resolve, reject) => {
599
+ const server = Bun.serve({
600
+ port,
601
+ hostname: "127.0.0.1",
602
+ fetch(req) {
603
+ const u = new URL(req.url);
604
+ const error = u.searchParams.get("error");
605
+ if (error) {
606
+ const desc = u.searchParams.get("error_description") ?? "";
607
+ finish(() => reject(new Error(`OAuth error: ${error} ${desc}`)));
608
+ return new Response("Authentication failed. You can close this tab.", {
609
+ status: 400
610
+ });
611
+ }
612
+ const code = u.searchParams.get("code");
613
+ const state = u.searchParams.get("state");
614
+ if (!code || state !== expectedState) {
615
+ finish(() => reject(new Error("OAuth state mismatch or missing code.")));
616
+ return new Response("Invalid OAuth response.", { status: 400 });
617
+ }
618
+ finish(() => resolve(code));
619
+ return new Response("<html><body>Authenticated. You can close this tab.</body></html>", { headers: { "content-type": "text/html" } });
620
+ }
621
+ });
622
+ const timer = setTimeout(() => {
623
+ finish(() => reject(new Error("OAuth flow timed out.")));
624
+ }, timeoutMs);
625
+ function finish(action) {
626
+ clearTimeout(timer);
627
+ setTimeout(() => server.stop(true), 50);
628
+ action();
629
+ }
630
+ });
631
+ }
632
+ async function exchangeCode(opts) {
633
+ const body = new URLSearchParams({
634
+ grant_type: "authorization_code",
635
+ code: opts.code,
636
+ redirect_uri: opts.redirectUri,
637
+ client_id: opts.clientId,
638
+ code_verifier: opts.verifier
639
+ });
640
+ const res = await fetch(opts.tokenEndpoint, {
641
+ method: "POST",
642
+ headers: { "content-type": "application/x-www-form-urlencoded" },
643
+ body
644
+ });
645
+ if (!res.ok) {
646
+ throw new Error(`OAuth token exchange failed: ${res.status} ${await res.text()}`);
647
+ }
648
+ return await res.json();
649
+ }
650
+ async function refreshTokens(opts) {
651
+ const body = new URLSearchParams({
652
+ grant_type: "refresh_token",
653
+ refresh_token: opts.refreshToken,
654
+ client_id: opts.clientId
655
+ });
656
+ const res = await fetch(opts.tokenEndpoint, {
657
+ method: "POST",
658
+ headers: { "content-type": "application/x-www-form-urlencoded" },
659
+ body
660
+ });
661
+ if (!res.ok) {
662
+ throw new Error(`OAuth refresh failed: ${res.status} ${await res.text()}`);
663
+ }
664
+ return await res.json();
665
+ }
666
+
667
+ // src/mcp/storage.ts
668
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
669
+ import { homedir } from "os";
670
+ import { dirname, join } from "path";
671
+ var DEFAULT_PATH = join(homedir(), ".roboport", "mcp-auth.json");
672
+
673
+ class FileStorage {
674
+ path;
675
+ cache;
676
+ constructor(path) {
677
+ this.path = path ?? DEFAULT_PATH;
678
+ }
679
+ async read() {
680
+ if (this.cache)
681
+ return this.cache;
682
+ try {
683
+ const raw = await readFile(this.path, "utf8");
684
+ this.cache = JSON.parse(raw);
685
+ } catch {
686
+ this.cache = {};
687
+ }
688
+ return this.cache;
689
+ }
690
+ async write(data) {
691
+ await mkdir(dirname(this.path), { recursive: true });
692
+ await writeFile(this.path, JSON.stringify(data, null, 2), "utf8");
693
+ await chmod(this.path, 384);
694
+ this.cache = data;
695
+ }
696
+ async load(key) {
697
+ const data = await this.read();
698
+ return data[key] ?? null;
699
+ }
700
+ async save(key, tokens) {
701
+ const data = await this.read();
702
+ data[key] = tokens;
703
+ await this.write(data);
704
+ }
705
+ async clear(key) {
706
+ const data = await this.read();
707
+ delete data[key];
708
+ await this.write(data);
709
+ }
710
+ }
711
+
712
+ class MemoryStorage {
713
+ data = new Map;
714
+ async load(key) {
715
+ return this.data.get(key) ?? null;
716
+ }
717
+ async save(key, tokens) {
718
+ this.data.set(key, tokens);
719
+ }
720
+ async clear(key) {
721
+ this.data.delete(key);
722
+ }
723
+ }
724
+
725
+ // src/mcp/auth.ts
726
+ class BearerAuth {
727
+ token;
728
+ constructor(token) {
729
+ this.token = token;
730
+ }
731
+ async getHeader() {
732
+ return `Bearer ${this.token}`;
733
+ }
734
+ }
735
+ var DEFAULT_PORT = 33418;
736
+ var DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
737
+
738
+ class OAuthAuth {
739
+ serverUrl;
740
+ storage;
741
+ storageKey;
742
+ redirectPort;
743
+ scopes;
744
+ flowTimeoutMs;
745
+ tokens;
746
+ loaded = false;
747
+ metadata;
748
+ inFlight;
749
+ constructor(opts) {
750
+ this.serverUrl = opts.serverUrl;
751
+ this.storage = opts.storage ?? new FileStorage;
752
+ this.storageKey = opts.storageKey;
753
+ this.redirectPort = opts.redirectPort ?? DEFAULT_PORT;
754
+ this.scopes = opts.scopes;
755
+ this.flowTimeoutMs = opts.flowTimeoutMs ?? DEFAULT_TIMEOUT_MS;
756
+ }
757
+ async getHeader() {
758
+ await this.ensureTokens();
759
+ if (!this.tokens)
760
+ throw new Error("OAuth: no tokens after auth flow.");
761
+ return `Bearer ${this.tokens.accessToken}`;
762
+ }
763
+ async onUnauthorized() {
764
+ if (this.tokens?.refreshToken) {
765
+ try {
766
+ await this.refresh();
767
+ return;
768
+ } catch {}
769
+ }
770
+ this.tokens = undefined;
771
+ await this.storage.clear(this.storageKey);
772
+ await this.authorize();
773
+ }
774
+ async ensureTokens() {
775
+ if (this.inFlight) {
776
+ await this.inFlight;
777
+ return;
778
+ }
779
+ this.inFlight = (async () => {
780
+ if (!this.loaded) {
781
+ this.tokens = await this.storage.load(this.storageKey) ?? undefined;
782
+ this.loaded = true;
783
+ }
784
+ if (!this.tokens) {
785
+ await this.authorize();
786
+ return;
787
+ }
788
+ if (this.isExpired(this.tokens)) {
789
+ if (this.tokens.refreshToken) {
790
+ try {
791
+ await this.refresh();
792
+ return;
793
+ } catch {}
794
+ }
795
+ await this.authorize();
796
+ }
797
+ })();
798
+ try {
799
+ await this.inFlight;
800
+ } finally {
801
+ this.inFlight = undefined;
802
+ }
803
+ }
804
+ isExpired(tokens) {
805
+ if (!tokens.expiresAt)
806
+ return false;
807
+ return Date.now() / 1000 >= tokens.expiresAt - 30;
808
+ }
809
+ async getMetadata() {
810
+ if (!this.metadata)
811
+ this.metadata = await discover(this.serverUrl);
812
+ return this.metadata;
813
+ }
814
+ async authorize() {
815
+ const meta = await this.getMetadata();
816
+ const redirectUri = this.tokens?.redirectUri ?? `http://127.0.0.1:${this.redirectPort}/callback`;
817
+ let clientId = this.tokens?.clientId;
818
+ if (!clientId) {
819
+ if (!meta.registration_endpoint) {
820
+ throw new Error("OAuth server does not support dynamic client registration; provide a client_id manually.");
821
+ }
822
+ clientId = await registerClient(meta.registration_endpoint, redirectUri);
823
+ }
824
+ const { verifier, challenge } = await generatePkce();
825
+ const state = generateState();
826
+ const port = parseInt(new URL(redirectUri).port, 10);
827
+ const codePromise = captureAuthorizationCode(port, state, this.flowTimeoutMs);
828
+ const authUrl = new URL(meta.authorization_endpoint);
829
+ authUrl.searchParams.set("response_type", "code");
830
+ authUrl.searchParams.set("client_id", clientId);
831
+ authUrl.searchParams.set("redirect_uri", redirectUri);
832
+ authUrl.searchParams.set("code_challenge", challenge);
833
+ authUrl.searchParams.set("code_challenge_method", "S256");
834
+ authUrl.searchParams.set("state", state);
835
+ if (this.scopes?.length) {
836
+ authUrl.searchParams.set("scope", this.scopes.join(" "));
837
+ }
838
+ console.error(`[mcp:oauth] Opening browser for ${this.storageKey} authentication...`);
839
+ openBrowser(authUrl.toString());
840
+ const code = await codePromise;
841
+ const response = await exchangeCode({
842
+ tokenEndpoint: meta.token_endpoint,
843
+ code,
844
+ clientId,
845
+ redirectUri,
846
+ verifier
847
+ });
848
+ this.tokens = this.toTokenSet(response, clientId, redirectUri);
849
+ await this.storage.save(this.storageKey, this.tokens);
850
+ }
851
+ async refresh() {
852
+ if (!this.tokens?.refreshToken || !this.tokens.clientId) {
853
+ throw new Error("OAuth refresh: missing refresh token or client id.");
854
+ }
855
+ const meta = await this.getMetadata();
856
+ const response = await refreshTokens({
857
+ tokenEndpoint: meta.token_endpoint,
858
+ refreshToken: this.tokens.refreshToken,
859
+ clientId: this.tokens.clientId
860
+ });
861
+ this.tokens = this.toTokenSet(response, this.tokens.clientId, this.tokens.redirectUri, this.tokens.refreshToken);
862
+ await this.storage.save(this.storageKey, this.tokens);
863
+ }
864
+ toTokenSet(response, clientId, redirectUri, fallbackRefresh) {
865
+ const expiresAt = response.expires_in ? Math.floor(Date.now() / 1000) + response.expires_in : undefined;
866
+ return {
867
+ accessToken: response.access_token,
868
+ refreshToken: response.refresh_token ?? fallbackRefresh,
869
+ expiresAt,
870
+ clientId,
871
+ redirectUri
872
+ };
873
+ }
874
+ }
875
+
876
+ // src/mcp/clients/grafana.ts
877
+ var EMPTY_OBJECT = {
878
+ type: "object",
879
+ properties: {},
880
+ additionalProperties: false
881
+ };
882
+ var TOOLS = [
883
+ {
884
+ name: "list_datasources",
885
+ description: "List all configured Grafana datasources.",
886
+ inputSchema: EMPTY_OBJECT,
887
+ call: (_, ctx) => request(ctx, "GET", "/api/datasources")
888
+ },
889
+ {
890
+ name: "get_datasource",
891
+ description: "Fetch a single datasource by UID.",
892
+ inputSchema: {
893
+ type: "object",
894
+ properties: {
895
+ uid: { type: "string", description: "Datasource UID." }
896
+ },
897
+ required: ["uid"],
898
+ additionalProperties: false
899
+ },
900
+ call: (args, ctx) => request(ctx, "GET", `/api/datasources/uid/${encodeURIComponent(String(args.uid))}`)
901
+ },
902
+ {
903
+ name: "query",
904
+ description: "Run one or more queries against Grafana datasources via /api/ds/query. Pass the query array as Grafana expects (each item needs refId and datasource.uid).",
905
+ inputSchema: {
906
+ type: "object",
907
+ properties: {
908
+ queries: {
909
+ type: "array",
910
+ description: "Array of query objects (refId, datasource, expr/range/etc.).",
911
+ items: { type: "object" }
912
+ },
913
+ from: {
914
+ type: "string",
915
+ description: 'Range start, e.g. "now-1h" or epoch ms as string.'
916
+ },
917
+ to: {
918
+ type: "string",
919
+ description: 'Range end, e.g. "now" or epoch ms as string.'
920
+ }
921
+ },
922
+ required: ["queries"],
923
+ additionalProperties: false
924
+ },
925
+ call: (args, ctx) => request(ctx, "POST", "/api/ds/query", {
926
+ queries: args.queries,
927
+ from: args.from ?? "now-1h",
928
+ to: args.to ?? "now"
929
+ })
930
+ },
931
+ {
932
+ name: "search_dashboards",
933
+ description: "Search dashboards by name, tag, or folder.",
934
+ inputSchema: {
935
+ type: "object",
936
+ properties: {
937
+ query: {
938
+ type: "string",
939
+ description: "Substring match on dashboard title."
940
+ },
941
+ tag: {
942
+ type: "array",
943
+ items: { type: "string" },
944
+ description: "Filter by tags."
945
+ },
946
+ folderUIDs: {
947
+ type: "array",
948
+ items: { type: "string" },
949
+ description: "Limit to specific folder UIDs."
950
+ },
951
+ limit: { type: "number", description: "Max results (default 100)." }
952
+ },
953
+ additionalProperties: false
954
+ },
955
+ call: (args, ctx) => {
956
+ const params = new URLSearchParams;
957
+ params.set("type", "dash-db");
958
+ if (typeof args.query === "string")
959
+ params.set("query", args.query);
960
+ if (typeof args.limit === "number")
961
+ params.set("limit", String(args.limit));
962
+ for (const tag of args.tag ?? [])
963
+ params.append("tag", tag);
964
+ for (const uid of args.folderUIDs ?? [])
965
+ params.append("folderUIDs", uid);
966
+ return request(ctx, "GET", `/api/search?${params.toString()}`);
967
+ }
968
+ },
969
+ {
970
+ name: "get_dashboard",
971
+ description: "Fetch a dashboard JSON by UID.",
972
+ inputSchema: {
973
+ type: "object",
974
+ properties: {
975
+ uid: { type: "string", description: "Dashboard UID." }
976
+ },
977
+ required: ["uid"],
978
+ additionalProperties: false
979
+ },
980
+ call: (args, ctx) => request(ctx, "GET", `/api/dashboards/uid/${encodeURIComponent(String(args.uid))}`)
981
+ },
982
+ {
983
+ name: "list_folders",
984
+ description: "List dashboard folders.",
985
+ inputSchema: EMPTY_OBJECT,
986
+ call: (_, ctx) => request(ctx, "GET", "/api/folders")
987
+ },
988
+ {
989
+ name: "list_alert_rules",
990
+ description: "List provisioned alert rules.",
991
+ inputSchema: EMPTY_OBJECT,
992
+ call: (_, ctx) => request(ctx, "GET", "/api/v1/provisioning/alert-rules")
993
+ }
994
+ ];
995
+ async function request(ctx, method, path, body) {
996
+ const headers = {
997
+ accept: "application/json",
998
+ authorization: await ctx.auth.getHeader()
999
+ };
1000
+ if (body !== undefined)
1001
+ headers["content-type"] = "application/json";
1002
+ const res = await fetch(`${ctx.baseUrl}${path}`, {
1003
+ method,
1004
+ headers,
1005
+ body: body !== undefined ? JSON.stringify(body) : undefined
1006
+ });
1007
+ const text = await res.text();
1008
+ if (!res.ok) {
1009
+ throw new Error(`Grafana ${method} ${path} failed: ${res.status} ${text}`);
1010
+ }
1011
+ if (!text)
1012
+ return null;
1013
+ try {
1014
+ return JSON.parse(text);
1015
+ } catch {
1016
+ return text;
1017
+ }
1018
+ }
1019
+
1020
+ class Mcp {
1021
+ baseUrl;
1022
+ auth;
1023
+ nameSpace;
1024
+ deferred;
1025
+ constructor(opts) {
1026
+ this.baseUrl = opts.url.replace(/\/$/, "");
1027
+ this.auth = new BearerAuth(opts.serviceAccountToken);
1028
+ this.nameSpace = opts.name ?? "grafana";
1029
+ this.deferred = opts.deferred ?? true;
1030
+ }
1031
+ async connect() {
1032
+ const ctx = { baseUrl: this.baseUrl, auth: this.auth };
1033
+ return TOOLS.map((def) => new Tool({
1034
+ name: `mcp__${this.nameSpace}__${def.name}`,
1035
+ description: def.description,
1036
+ jsonSchema: def.inputSchema,
1037
+ deferred: this.deferred,
1038
+ execute: async (input) => {
1039
+ const args = input ?? {};
1040
+ const result = await def.call(args, ctx);
1041
+ return typeof result === "string" ? result : JSON.stringify(result);
1042
+ }
1043
+ }));
1044
+ }
1045
+ async disconnect() {}
1046
+ }
1047
+ var grafana_default = Mcp;
1048
+
1049
+ // src/mcp/core.ts
1050
+ var PROTOCOL_VERSION = "2024-11-05";
1051
+
1052
+ class StdioTransport {
1053
+ config;
1054
+ proc;
1055
+ nextId = 1;
1056
+ pending = new Map;
1057
+ buffer = "";
1058
+ constructor(config) {
1059
+ this.config = config;
1060
+ }
1061
+ async start() {
1062
+ this.proc = Bun.spawn([this.config.command, ...this.config.args ?? []], {
1063
+ stdin: "pipe",
1064
+ stdout: "pipe",
1065
+ stderr: "inherit",
1066
+ env: { ...process.env, ...this.config.env }
1067
+ });
1068
+ this.readLoop();
1069
+ }
1070
+ async readLoop() {
1071
+ if (!this.proc?.stdout)
1072
+ return;
1073
+ const reader = this.proc.stdout.getReader();
1074
+ const decoder = new TextDecoder;
1075
+ while (true) {
1076
+ const { value, done } = await reader.read();
1077
+ if (done)
1078
+ break;
1079
+ this.buffer += decoder.decode(value, { stream: true });
1080
+ let idx = this.buffer.indexOf(`
1081
+ `);
1082
+ while (idx !== -1) {
1083
+ const line = this.buffer.slice(0, idx).trim();
1084
+ this.buffer = this.buffer.slice(idx + 1);
1085
+ if (line)
1086
+ this.handleLine(line);
1087
+ idx = this.buffer.indexOf(`
1088
+ `);
1089
+ }
1090
+ }
1091
+ for (const { reject } of this.pending.values()) {
1092
+ reject(new Error("MCP stdio transport closed."));
1093
+ }
1094
+ this.pending.clear();
1095
+ }
1096
+ handleLine(line) {
1097
+ let msg;
1098
+ try {
1099
+ msg = JSON.parse(line);
1100
+ } catch {
1101
+ return;
1102
+ }
1103
+ if (msg.id === null || msg.id === undefined)
1104
+ return;
1105
+ const id = typeof msg.id === "string" ? parseInt(msg.id, 10) : msg.id;
1106
+ const pending = this.pending.get(id);
1107
+ if (!pending)
1108
+ return;
1109
+ this.pending.delete(id);
1110
+ if (msg.error) {
1111
+ pending.reject(new Error(`MCP error ${msg.error.code}: ${msg.error.message}`));
1112
+ } else {
1113
+ pending.resolve(msg.result);
1114
+ }
1115
+ }
1116
+ async stop() {
1117
+ this.proc?.kill();
1118
+ if (this.proc)
1119
+ await this.proc.exited;
1120
+ this.proc = undefined;
1121
+ }
1122
+ writeLine(line) {
1123
+ const sink = this.proc?.stdin;
1124
+ if (!sink || typeof sink === "number") {
1125
+ throw new Error("MCP stdio transport not started.");
1126
+ }
1127
+ sink.write(`${line}
1128
+ `);
1129
+ sink.flush();
1130
+ }
1131
+ async request(method, params) {
1132
+ const id = this.nextId++;
1133
+ const req = { jsonrpc: "2.0", id, method, params };
1134
+ return new Promise((resolve, reject) => {
1135
+ this.pending.set(id, { resolve, reject });
1136
+ try {
1137
+ this.writeLine(JSON.stringify(req));
1138
+ } catch (error) {
1139
+ this.pending.delete(id);
1140
+ reject(error);
1141
+ }
1142
+ });
1143
+ }
1144
+ async notify(method, params) {
1145
+ const req = { jsonrpc: "2.0", method, params };
1146
+ this.writeLine(JSON.stringify(req));
1147
+ }
1148
+ }
1149
+
1150
+ class HttpTransport {
1151
+ config;
1152
+ nextId = 1;
1153
+ sessionId;
1154
+ constructor(config) {
1155
+ this.config = config;
1156
+ }
1157
+ async start() {}
1158
+ async stop() {}
1159
+ async buildHeaders() {
1160
+ const headers = {
1161
+ "content-type": "application/json",
1162
+ accept: "application/json, text/event-stream",
1163
+ ...this.config.headers ?? {}
1164
+ };
1165
+ if (this.sessionId)
1166
+ headers["mcp-session-id"] = this.sessionId;
1167
+ if (this.config.auth) {
1168
+ headers["authorization"] = await this.config.auth.getHeader();
1169
+ }
1170
+ return headers;
1171
+ }
1172
+ async send(body, expectId) {
1173
+ const res = await this.attempt(body);
1174
+ if (res.status === 401 && this.config.auth?.onUnauthorized) {
1175
+ await res.body?.cancel();
1176
+ await this.config.auth.onUnauthorized();
1177
+ const retried = await this.attempt(body);
1178
+ return this.consume(retried, expectId);
1179
+ }
1180
+ return this.consume(res, expectId);
1181
+ }
1182
+ async attempt(body) {
1183
+ return fetch(this.config.url, {
1184
+ method: "POST",
1185
+ headers: await this.buildHeaders(),
1186
+ body: JSON.stringify(body)
1187
+ });
1188
+ }
1189
+ async consume(res, expectId) {
1190
+ if (!res.ok) {
1191
+ throw new Error(`MCP HTTP error ${res.status}: ${await res.text()}`);
1192
+ }
1193
+ const sid = res.headers.get("mcp-session-id");
1194
+ if (sid)
1195
+ this.sessionId = sid;
1196
+ if (expectId === undefined) {
1197
+ await res.body?.cancel();
1198
+ return;
1199
+ }
1200
+ const contentType = res.headers.get("content-type") ?? "";
1201
+ if (contentType.includes("text/event-stream")) {
1202
+ return this.readSseResponse(res, expectId);
1203
+ }
1204
+ const json = await res.json();
1205
+ return this.unwrap(json, expectId);
1206
+ }
1207
+ async readSseResponse(res, expectId) {
1208
+ const text = await res.text();
1209
+ for (const block of text.split(/\n\n+/)) {
1210
+ const dataLines = block.split(`
1211
+ `).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
1212
+ if (dataLines.length === 0)
1213
+ continue;
1214
+ const payload = dataLines.join(`
1215
+ `);
1216
+ let json;
1217
+ try {
1218
+ json = JSON.parse(payload);
1219
+ } catch {
1220
+ continue;
1221
+ }
1222
+ const id = typeof json.id === "string" ? parseInt(json.id, 10) : json.id;
1223
+ if (id === expectId)
1224
+ return this.unwrap(json, expectId);
1225
+ }
1226
+ throw new Error("MCP HTTP SSE response missing expected id.");
1227
+ }
1228
+ unwrap(json, expectId) {
1229
+ if (json.error) {
1230
+ throw new Error(`MCP error ${json.error.code}: ${json.error.message}`);
1231
+ }
1232
+ const id = typeof json.id === "string" ? parseInt(json.id, 10) : json.id;
1233
+ if (id !== expectId) {
1234
+ throw new Error(`MCP HTTP response id mismatch (got ${id}).`);
1235
+ }
1236
+ return json.result;
1237
+ }
1238
+ async request(method, params) {
1239
+ const id = this.nextId++;
1240
+ const req = { jsonrpc: "2.0", id, method, params };
1241
+ return this.send(req, id);
1242
+ }
1243
+ async notify(method, params) {
1244
+ const req = { jsonrpc: "2.0", method, params };
1245
+ await this.send(req, undefined);
1246
+ }
1247
+ }
1248
+ function formatContent(content) {
1249
+ if (content.length === 1 && content[0]?.type === "text") {
1250
+ return content[0].text ?? "";
1251
+ }
1252
+ return content.map((block) => block.type === "text" ? block.text ?? "" : JSON.stringify(block)).join(`
1253
+ `);
1254
+ }
1255
+
1256
+ class Mcp2 {
1257
+ name;
1258
+ transport;
1259
+ deferred;
1260
+ connection;
1261
+ constructor({
1262
+ name,
1263
+ transport,
1264
+ deferred
1265
+ }) {
1266
+ this.name = name;
1267
+ this.transport = transport;
1268
+ this.deferred = deferred ?? true;
1269
+ }
1270
+ async connect() {
1271
+ const connection = this.transport.type === "stdio" ? new StdioTransport(this.transport) : new HttpTransport(this.transport);
1272
+ this.connection = connection;
1273
+ await connection.start();
1274
+ await connection.request("initialize", {
1275
+ protocolVersion: PROTOCOL_VERSION,
1276
+ capabilities: {},
1277
+ clientInfo: { name: "roboport", version: "0.1.0" }
1278
+ });
1279
+ await connection.notify("notifications/initialized");
1280
+ const result = await connection.request("tools/list");
1281
+ return result.tools.map((remote) => this.wrap(remote));
1282
+ }
1283
+ async disconnect() {
1284
+ if (!this.connection)
1285
+ return;
1286
+ const connection = this.connection;
1287
+ this.connection = undefined;
1288
+ await connection.stop();
1289
+ }
1290
+ wrap(remote) {
1291
+ return new Tool({
1292
+ name: `mcp__${this.name}__${remote.name}`,
1293
+ description: remote.description ?? `${this.name}.${remote.name}`,
1294
+ jsonSchema: remote.inputSchema,
1295
+ deferred: this.deferred,
1296
+ execute: async (input) => {
1297
+ if (!this.connection) {
1298
+ throw new Error(`MCP "${this.name}" is not connected.`);
1299
+ }
1300
+ const result = await this.connection.request("tools/call", {
1301
+ name: remote.name,
1302
+ arguments: input
1303
+ });
1304
+ const text = formatContent(result.content);
1305
+ if (result.isError)
1306
+ throw new Error(text);
1307
+ return text;
1308
+ }
1309
+ });
1310
+ }
1311
+ }
1312
+
1313
+ // src/mcp/clients/linear.ts
1314
+ var LINEAR_MCP_URL = "https://mcp.linear.app/mcp";
1315
+
1316
+ class Mcp3 extends Mcp2 {
1317
+ constructor(opts) {
1318
+ super({
1319
+ name: opts.name ?? "linear",
1320
+ transport: {
1321
+ type: "http",
1322
+ url: LINEAR_MCP_URL,
1323
+ auth: new BearerAuth(opts.apiKey)
1324
+ }
1325
+ });
1326
+ }
1327
+ }
1328
+ var linear_default = Mcp3;
1329
+
1330
+ // src/mcp/clients/tenderly.ts
1331
+ var TENDERLY_MCP_URL = "https://mcp.tenderly.co/mcp";
1332
+
1333
+ class Mcp4 extends Mcp2 {
1334
+ constructor(opts) {
1335
+ const auth = new OAuthAuth({
1336
+ serverUrl: TENDERLY_MCP_URL,
1337
+ storageKey: "tenderly",
1338
+ storage: opts?.storage,
1339
+ redirectPort: opts?.redirectPort
1340
+ });
1341
+ super({
1342
+ name: opts?.name ?? "tenderly",
1343
+ transport: {
1344
+ type: "http",
1345
+ url: TENDERLY_MCP_URL,
1346
+ auth
1347
+ }
1348
+ });
1349
+ }
1350
+ }
1351
+ var tenderly_default = Mcp4;
1352
+ export {
1353
+ tenderly_default as Tenderly,
1354
+ linear_default as Linear,
1355
+ grafana_default as Grafana
1356
+ };