@borgresearch/odin-mcp 0.1.0 → 0.3.0

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/index.js CHANGED
@@ -1,558 +1,33 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ PACKAGE_VERSION,
4
+ createOdinMcpServer
5
+ } from "./chunk-ODZB3YP2.js";
6
+ import "./chunk-4LCKW236.js";
7
+ import "./chunk-D5FYYTYV.js";
8
+ import {
9
+ DEFAULT_ODIN_BASE_URL,
10
+ OdinClient
11
+ } from "./chunk-XHJYVUHG.js";
2
12
 
3
13
  // src/index.ts
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
14
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
-
7
- // src/client.ts
8
- var OdinApiError = class extends Error {
9
- status;
10
- path;
11
- method;
12
- retryAfterSeconds;
13
- body;
14
- constructor(params) {
15
- super(params.message);
16
- this.name = "OdinApiError";
17
- this.status = params.status;
18
- this.path = params.path;
19
- this.method = params.method;
20
- this.retryAfterSeconds = params.retryAfterSeconds;
21
- this.body = params.body;
22
- }
23
- };
24
- var DEFAULT_ODIN_BASE_URL = "https://odin.borghq.io";
25
- var WRITE_METHODS = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
26
- var OdinClient = class {
27
- baseUrl;
28
- apiKey;
29
- fetchImpl;
30
- constructor(options) {
31
- if (!options.apiKey) {
32
- throw new Error("OdinClient requires an apiKey");
33
- }
34
- this.apiKey = options.apiKey;
35
- this.baseUrl = (options.baseUrl ?? DEFAULT_ODIN_BASE_URL).replace(
36
- /\/+$/,
37
- ""
38
- );
39
- this.fetchImpl = options.fetchImpl ?? fetch;
40
- }
41
- async get(path, searchParams) {
42
- const url = this.buildUrl(path, searchParams);
43
- return this.request("GET", url, path);
44
- }
45
- async patch(path, body) {
46
- const url = this.buildUrl(path);
47
- return this.request("PATCH", url, path, body);
48
- }
49
- async post(path, body) {
50
- const url = this.buildUrl(path);
51
- return this.request("POST", url, path, body);
52
- }
53
- buildUrl(path, searchParams) {
54
- const url = new URL(path.startsWith("/") ? path : `/${path}`, this.baseUrl);
55
- if (searchParams) {
56
- for (const [key, value] of Object.entries(searchParams)) {
57
- if (value === void 0 || value === "") continue;
58
- url.searchParams.set(key, String(value));
59
- }
60
- }
61
- return url.toString();
62
- }
63
- async request(method, url, path, body) {
64
- const response = await this.fetchImpl(url, {
65
- method,
66
- headers: {
67
- Authorization: `Bearer ${this.apiKey}`,
68
- "Content-Type": "application/json",
69
- Accept: "application/json"
70
- },
71
- body: body === void 0 ? void 0 : JSON.stringify(body)
72
- });
73
- const rawText = await response.text();
74
- const parsed = rawText ? safeJsonParse(rawText) : null;
75
- if (!response.ok) {
76
- const retryAfterSeconds = parseRetryAfter(
77
- response.headers.get("Retry-After")
78
- );
79
- throw new OdinApiError({
80
- status: response.status,
81
- path,
82
- method,
83
- retryAfterSeconds,
84
- body: parsed ?? rawText,
85
- message: formatErrorMessage({
86
- status: response.status,
87
- method,
88
- path,
89
- retryAfterSeconds,
90
- parsed,
91
- rawText
92
- })
93
- });
94
- }
95
- return parsed;
96
- }
97
- };
98
- function safeJsonParse(text) {
99
- try {
100
- return JSON.parse(text);
101
- } catch {
102
- return text;
103
- }
104
- }
105
- function parseRetryAfter(header) {
106
- if (!header) return null;
107
- const trimmed = header.trim();
108
- if (/^\d+$/.test(trimmed)) return Number.parseInt(trimmed, 10);
109
- const dateMs = Date.parse(trimmed);
110
- if (Number.isNaN(dateMs)) return null;
111
- const seconds = Math.ceil((dateMs - Date.now()) / 1e3);
112
- return seconds > 0 ? seconds : null;
113
- }
114
- function formatErrorMessage(input) {
115
- const serverMessage = extractServerMessage(input.parsed);
116
- const actionable = actionableMessage(input);
117
- if (actionable) {
118
- return serverMessage ? `${actionable} (server: ${serverMessage})` : actionable;
119
- }
120
- const suffix = serverMessage ? ` \u2014 ${serverMessage}` : input.rawText ? ` \u2014 ${input.rawText.slice(0, 200)}` : "";
121
- return `Odin API ${input.status} on ${input.path}${suffix}.`;
122
- }
123
- function actionableMessage(input) {
124
- switch (input.status) {
125
- case 401:
126
- return "ODIN_API_KEY is not set or invalid. Create one at Management > API Keys in the Odin dashboard.";
127
- case 403:
128
- if (WRITE_METHODS.has(input.method)) {
129
- return "API key does not have write permission. Create a new key with 'Read & Write' permissions.";
130
- }
131
- return null;
132
- case 404:
133
- if (isFindingDetailPath(input.path)) {
134
- return "Finding not found. Check the finding ID is correct.";
135
- }
136
- return null;
137
- case 429: {
138
- const n = input.retryAfterSeconds;
139
- return n ? `Rate limit exceeded. Try again in ${n} seconds.` : "Rate limit exceeded. Try again shortly.";
140
- }
141
- default:
142
- return null;
143
- }
144
- }
145
- function isFindingDetailPath(path) {
146
- return /^\/api\/findings\/[^/]+(\/.*)?$/.test(path);
147
- }
148
- function extractServerMessage(parsed) {
149
- if (parsed && typeof parsed === "object" && "error" in parsed) {
150
- const err = parsed.error;
151
- if (typeof err === "string") return err;
152
- }
153
- return null;
154
- }
155
-
156
- // src/tools/get-asset.ts
157
- import { z } from "zod";
158
-
159
- // src/formatters/markdown.ts
160
- function formatFindingsList(data) {
161
- const { findings, stats, pagination } = data;
162
- if (findings.length === 0) {
163
- return "No findings match the supplied filters.";
164
- }
165
- const rows = findings.map((f) => {
166
- const severity = `${f.severity.label} (CVSS ${f.severity.cvss.toFixed(1)})`;
167
- return `| ${f.findingId} | ${escapePipes(f.title)} | ${severity} | ${f.status} | ${formatDate(f.createdAt)} |`;
168
- });
169
- return [
170
- `Showing ${findings.length} of ${stats.total} finding(s). Offset ${pagination.offset}, limit ${pagination.limit}. hasMore=${pagination.hasMore}.`,
171
- "",
172
- "| Finding ID | Title | Severity | Status | Created |",
173
- "| --- | --- | --- | --- | --- |",
174
- ...rows
175
- ].join("\n");
176
- }
177
- function formatFindingDetail(data) {
178
- const { finding, revision } = data;
179
- const severity = `${finding.severity.label} (CVSS ${finding.severity.cvss.toFixed(1)})`;
180
- const header = [
181
- `# ${finding.title}`,
182
- "",
183
- `- **ID:** ${finding.findingId}`,
184
- `- **Severity:** ${severity}`,
185
- `- **Status:** ${finding.status}`,
186
- `- **Pentest:** ${finding.context?.pentestId ?? "standalone"}`,
187
- `- **Created:** ${formatDate(finding.createdAt)}`,
188
- `- **Updated:** ${formatDate(finding.updatedAt)}`
189
- ];
190
- if (!revision) {
191
- return [...header, "", "_No revision content available._"].join("\n");
192
- }
193
- const c = revision.content;
194
- const sections = [];
195
- if (c.summaryMd) sections.push("## Summary", c.summaryMd);
196
- if (c.descriptionMd) sections.push("## Description", c.descriptionMd);
197
- if (c.businessImpactMd)
198
- sections.push("## Business Impact", c.businessImpactMd);
199
- if (c.detailsMd) sections.push("## Details", c.detailsMd);
200
- if (c.remediationMd) sections.push("## Remediation", c.remediationMd);
201
- if (c.affectedAssets?.length) {
202
- sections.push(
203
- "## Affected Assets",
204
- c.affectedAssets.map((a) => `- ${a.display} (${a.assetId})`).join("\n")
205
- );
206
- }
207
- return [
208
- ...header,
209
- "",
210
- `_Revision ${revision.revisionNumber}_`,
211
- "",
212
- ...sections
213
- ].join("\n");
214
- }
215
- function formatFindingStatusUpdate(finding) {
216
- return [
217
- `Updated finding **${finding.findingId}** status to **${finding.status}**.`,
218
- `Title: ${finding.title}`
219
- ].join("\n");
220
- }
221
- function formatPentestsList(data) {
222
- if (data.pentests.length === 0) return "No pentests found.";
223
- const rows = data.pentests.map(formatPentestRow);
224
- return [
225
- "| Pentest ID | Name | Status | Start | End | Scope |",
226
- "| --- | --- | --- | --- | --- | --- |",
227
- ...rows
228
- ].join("\n");
229
- }
230
- function formatPentestDetail(data) {
231
- const p = data.pentest;
232
- const end = p.endAt ? formatDate(p.endAt) : "open";
233
- const scope = p.scope.length ? p.scope.map((s) => `- ${s}`).join("\n") : "_No scope defined._";
234
- return [
235
- `# ${p.name}`,
236
- "",
237
- `- **ID:** ${p.pentestId}`,
238
- `- **Status:** ${p.status}`,
239
- `- **Start:** ${formatDate(p.startAt)}`,
240
- `- **End:** ${end}`,
241
- `- **Created:** ${formatDate(p.createdAt)}`,
242
- "",
243
- "## Scope",
244
- "",
245
- scope
246
- ].join("\n");
247
- }
248
- function formatPentestRow(p) {
249
- const end = p.endAt ? formatDate(p.endAt) : "open";
250
- const scope = p.scope.slice(0, 3).join(", ") + (p.scope.length > 3 ? ", \u2026" : "");
251
- return `| ${p.pentestId} | ${escapePipes(p.name)} | ${p.status} | ${formatDate(p.startAt)} | ${end} | ${escapePipes(scope)} |`;
252
- }
253
- function formatAssetsList(data) {
254
- const { assets, stats, pagination } = data;
255
- if (assets.length === 0) return "No assets match the supplied filters.";
256
- const rows = assets.map(formatAssetRow);
257
- return [
258
- `Showing ${assets.length} of ${stats.total} asset(s). Offset ${pagination.offset}, limit ${pagination.limit}. hasMore=${pagination.hasMore}.`,
259
- "",
260
- "| Asset ID | Type | Value | Source | Status | Last seen |",
261
- "| --- | --- | --- | --- | --- | --- |",
262
- ...rows
263
- ].join("\n");
264
- }
265
- function formatAssetDetail(data) {
266
- const { asset, findings } = data;
267
- const header = [
268
- `# ${asset.value}`,
269
- "",
270
- `- **ID:** ${asset.assetId}`,
271
- `- **Type:** ${asset.type}`,
272
- `- **Source:** ${asset.source}`,
273
- `- **Status:** ${asset.status}`,
274
- `- **Last seen:** ${formatDate(asset.lastSeenAt)}`,
275
- `- **Created:** ${formatDate(asset.createdAt)}`
276
- ];
277
- if (findings.length === 0) {
278
- return [...header, "", "_No findings reference this asset._"].join("\n");
279
- }
280
- const rows = findings.map((f) => {
281
- const severity = `${f.severity.label} (CVSS ${f.severity.cvss.toFixed(1)})`;
282
- return `| ${f.findingId} | ${escapePipes(f.title)} | ${severity} | ${f.status} | ${formatDate(f.createdAt)} |`;
283
- });
284
- return [
285
- ...header,
286
- "",
287
- `## Findings (${findings.length})`,
288
- "",
289
- "| Finding ID | Title | Severity | Status | Created |",
290
- "| --- | --- | --- | --- | --- |",
291
- ...rows
292
- ].join("\n");
293
- }
294
- function formatAssetRow(a) {
295
- return `| ${a.assetId} | ${a.type} | ${escapePipes(a.value)} | ${a.source} | ${a.status} | ${formatDate(a.lastSeenAt)} |`;
296
- }
297
- function escapePipes(value) {
298
- return value.replace(/\|/g, "\\|");
299
- }
300
- function formatDate(epoch) {
301
- if (!epoch) return "\u2014";
302
- const ms = epoch < 1e12 ? epoch * 1e3 : epoch;
303
- return new Date(ms).toISOString().slice(0, 10);
304
- }
305
-
306
- // src/tools/get-asset.ts
307
- var getAssetInput = z.object({
308
- assetId: z.string().min(1)
309
- });
310
- var getAssetTool = {
311
- name: "get_asset",
312
- title: "Get asset",
313
- description: "Fetch a single attack-surface asset by ID, including the findings that reference it in their latest revision.",
314
- inputSchema: getAssetInput,
315
- handler: (client) => async (params) => {
316
- const data = await client.get(
317
- `/api/assets/${encodeURIComponent(params.assetId)}`
318
- );
319
- return {
320
- markdown: formatAssetDetail(data),
321
- structured: { asset: data.asset, findings: data.findings }
322
- };
323
- }
324
- };
325
-
326
- // src/tools/get-finding.ts
327
- import { z as z2 } from "zod";
328
- var getFindingInput = z2.object({
329
- findingId: z2.string().min(1)
330
- });
331
- var getFindingTool = {
332
- name: "get_finding",
333
- title: "Get finding",
334
- description: "Fetch a single finding by ID, including the latest revision content (description, impact, details, remediation).",
335
- inputSchema: getFindingInput,
336
- handler: (client) => async (params) => {
337
- const data = await client.get(
338
- `/api/findings/${encodeURIComponent(params.findingId)}`
339
- );
340
- return {
341
- markdown: formatFindingDetail(data),
342
- structured: {
343
- finding: data.finding,
344
- revision: data.revision
345
- }
346
- };
347
- }
348
- };
349
-
350
- // src/tools/get-pentest.ts
351
- import { z as z3 } from "zod";
352
- var getPentestInput = z3.object({
353
- pentestId: z3.string().min(1)
354
- });
355
- var getPentestTool = {
356
- name: "get_pentest",
357
- title: "Get pentest",
358
- description: "Fetch a single pentest by ID, including status, scheduled window, and in-scope targets.",
359
- inputSchema: getPentestInput,
360
- handler: (client) => async (params) => {
361
- const data = await client.get(
362
- `/api/pentests/${encodeURIComponent(params.pentestId)}`
363
- );
364
- return {
365
- markdown: formatPentestDetail(data),
366
- structured: { pentest: data.pentest }
367
- };
368
- }
369
- };
370
-
371
- // src/tools/list-assets.ts
372
- import { z as z4 } from "zod";
373
- var listAssetsInput = z4.object({
374
- type: z4.enum(["DOMAIN", "SUBDOMAIN", "IP", "URL", "WILDCARD"]).optional(),
375
- source: z4.enum(["MANUAL", "RECON", "IMPORT", "AGENT"]).optional(),
376
- status: z4.string().optional().describe(
377
- "Dynamic asset status set by recon (e.g. ACTIVE, INACTIVE, TLS_ERROR)."
378
- ),
379
- search: z4.string().optional(),
380
- sortBy: z4.enum(["lastSeenAt", "createdAt", "value"]).optional(),
381
- sortOrder: z4.enum(["asc", "desc"]).optional(),
382
- limit: z4.number().int().min(1).max(100).optional().default(50),
383
- offset: z4.number().int().min(0).optional().default(0)
384
- });
385
- var listAssetsTool = {
386
- name: "list_assets",
387
- title: "List assets",
388
- description: "List attack-surface assets for the organisation. Supports filtering by type, source, status, and free-text search.",
389
- inputSchema: listAssetsInput,
390
- handler: (client) => async (params) => {
391
- const data = await client.get("/api/assets", params);
392
- return {
393
- markdown: formatAssetsList(data),
394
- structured: {
395
- assets: data.assets,
396
- stats: data.stats,
397
- pagination: data.pagination
398
- }
399
- };
400
- }
401
- };
402
-
403
- // src/tools/list-findings.ts
404
- import { z as z5 } from "zod";
405
- var listFindingsInput = z5.object({
406
- pentestId: z5.string().optional().describe("Filter to a single pentest."),
407
- assetId: z5.string().optional().describe(
408
- "Filter to findings whose latest revision references this asset."
409
- ),
410
- severity: z5.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFORMATIONAL"]).optional(),
411
- status: z5.enum([
412
- "DRAFT",
413
- "IN_REVIEW",
414
- "REPORTED",
415
- "MITIGATING",
416
- "OPEN_FOR_RETEST",
417
- "RESOLVED",
418
- "NOT_REPRODUCIBLE",
419
- "ACCEPTED_RISK"
420
- ]).optional(),
421
- search: z5.string().optional().describe("Free-text search on finding titles."),
422
- sortBy: z5.enum(["severity", "createdAt"]).optional(),
423
- sortOrder: z5.enum(["asc", "desc"]).optional(),
424
- limit: z5.number().int().min(1).max(200).optional().default(50),
425
- offset: z5.number().int().min(0).optional().default(0)
426
- });
427
- var listFindingsTool = {
428
- name: "list_findings",
429
- title: "List findings",
430
- description: "List security findings for the organisation. Supports filtering by pentest, severity, status, and free-text search.",
431
- inputSchema: listFindingsInput,
432
- handler: (client) => async (params) => {
433
- const data = await client.get(
434
- "/api/findings",
435
- params
436
- );
437
- return {
438
- markdown: formatFindingsList(data),
439
- structured: {
440
- findings: data.findings,
441
- stats: data.stats,
442
- pagination: data.pagination
443
- }
444
- };
445
- }
446
- };
447
-
448
- // src/tools/list-pentests.ts
449
- import { z as z6 } from "zod";
450
- var listPentestsInput = z6.object({});
451
- var listPentestsTool = {
452
- name: "list_pentests",
453
- title: "List pentests",
454
- description: "List all pentests for the organisation, including status, scheduled window, and in-scope targets.",
455
- inputSchema: listPentestsInput,
456
- handler: (client) => async () => {
457
- const data = await client.get("/api/pentests");
458
- return {
459
- markdown: formatPentestsList(data),
460
- structured: { pentests: data.pentests }
461
- };
462
- }
463
- };
464
-
465
- // src/tools/update-finding-status.ts
466
- import { z as z7 } from "zod";
467
- var updateFindingStatusInput = z7.object({
468
- findingId: z7.string().min(1),
469
- status: z7.enum(["MITIGATING", "OPEN_FOR_RETEST", "ACKNOWLEDGED"]).describe(
470
- "Target status. Only MITIGATING, OPEN_FOR_RETEST, and ACKNOWLEDGED are client-writable; transitions are only allowed from REPORTED, MITIGATING, or OPEN_FOR_RETEST. ACKNOWLEDGED marks the finding as accepted-risk and is terminal."
471
- )
472
- });
473
- var updateFindingStatusTool = {
474
- name: "update_finding_status",
475
- title: "Update finding status",
476
- description: "Change a finding's status. Requires an API key with write permission and member role. Only MITIGATING, OPEN_FOR_RETEST, and ACKNOWLEDGED are accepted.",
477
- inputSchema: updateFindingStatusInput,
478
- handler: (client) => async (params) => {
479
- const data = await client.patch(
480
- `/api/findings/${encodeURIComponent(params.findingId)}/status`,
481
- { status: params.status }
482
- );
483
- return {
484
- markdown: formatFindingStatusUpdate(data.finding),
485
- structured: { finding: data.finding }
486
- };
487
- }
488
- };
489
-
490
- // src/index.ts
491
- var PACKAGE_VERSION = "0.1.0";
492
15
  async function main() {
493
16
  const apiKey = process.env.ODIN_API_KEY;
494
17
  if (!apiKey) {
495
- console.error(
496
- "ODIN_API_KEY is not set or invalid. Create one at Management > API Keys in the Odin dashboard."
18
+ throw new Error(
19
+ "ODIN_API_KEY is required. Create a key under Management > API Keys."
497
20
  );
498
- process.exit(1);
499
21
  }
500
22
  const baseUrl = process.env.ODIN_BASE_URL ?? DEFAULT_ODIN_BASE_URL;
501
- const client = new OdinClient({ apiKey, baseUrl });
502
- const server = new McpServer({
503
- name: "@borgresearch/odin-mcp",
504
- version: PACKAGE_VERSION
23
+ const client = new OdinClient({
24
+ apiKey,
25
+ baseUrl,
26
+ vercelProtectionBypassSecret: process.env.ODIN_VERCEL_PROTECTION_BYPASS_SECRET
505
27
  });
506
- const tools = [
507
- listFindingsTool,
508
- getFindingTool,
509
- updateFindingStatusTool,
510
- listPentestsTool,
511
- getPentestTool,
512
- listAssetsTool,
513
- getAssetTool
514
- ];
515
- for (const tool of tools) {
516
- const run = tool.handler(client);
517
- server.registerTool(
518
- tool.name,
519
- {
520
- title: tool.title,
521
- description: tool.description,
522
- inputSchema: tool.inputSchema.shape
523
- },
524
- async (params) => {
525
- try {
526
- const { markdown, structured } = await run(params);
527
- return {
528
- content: [{ type: "text", text: markdown }],
529
- structuredContent: { markdown, ...toObject(structured) }
530
- };
531
- } catch (error) {
532
- return {
533
- isError: true,
534
- content: [{ type: "text", text: toErrorMessage(error) }]
535
- };
536
- }
537
- }
538
- );
539
- }
540
- const transport = new StdioServerTransport();
541
- await server.connect(transport);
542
- console.error(
543
- `odin-mcp ${PACKAGE_VERSION} connected (baseUrl=${baseUrl}, tools=${tools.length}).`
544
- );
545
- }
546
- function toObject(value) {
547
- if (value && typeof value === "object" && !Array.isArray(value)) {
548
- return value;
549
- }
550
- return { data: value };
551
- }
552
- function toErrorMessage(error) {
553
- if (error instanceof OdinApiError) return error.message;
554
- if (error instanceof Error) return `Unexpected error: ${error.message}`;
555
- return `Unexpected error: ${String(error)}`;
28
+ const server = createOdinMcpServer(client, { appUrl: baseUrl });
29
+ await server.connect(new StdioServerTransport());
30
+ console.error(`odin-mcp ${PACKAGE_VERSION} connected over stdio.`);
556
31
  }
557
32
  main().catch((error) => {
558
33
  console.error("odin-mcp failed to start:", error);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/tools/get-asset.ts","../src/formatters/markdown.ts","../src/tools/get-finding.ts","../src/tools/get-pentest.ts","../src/tools/list-assets.ts","../src/tools/list-findings.ts","../src/tools/list-pentests.ts","../src/tools/update-finding-status.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { DEFAULT_ODIN_BASE_URL, OdinApiError, OdinClient } from \"./client.js\";\nimport { getAssetTool } from \"./tools/get-asset.js\";\nimport { getFindingTool } from \"./tools/get-finding.js\";\nimport { getPentestTool } from \"./tools/get-pentest.js\";\nimport { listAssetsTool } from \"./tools/list-assets.js\";\nimport { listFindingsTool } from \"./tools/list-findings.js\";\nimport { listPentestsTool } from \"./tools/list-pentests.js\";\nimport { updateFindingStatusTool } from \"./tools/update-finding-status.js\";\n\nconst PACKAGE_VERSION = \"0.1.0\";\n\ninterface ToolResult {\n markdown: string;\n structured: unknown;\n}\n\nasync function main() {\n const apiKey = process.env.ODIN_API_KEY;\n if (!apiKey) {\n console.error(\n \"ODIN_API_KEY is not set or invalid. Create one at Management > API Keys in the Odin dashboard.\",\n );\n process.exit(1);\n }\n\n const baseUrl = process.env.ODIN_BASE_URL ?? DEFAULT_ODIN_BASE_URL;\n const client = new OdinClient({ apiKey, baseUrl });\n\n const server = new McpServer({\n name: \"@borgresearch/odin-mcp\",\n version: PACKAGE_VERSION,\n });\n\n const tools = [\n listFindingsTool,\n getFindingTool,\n updateFindingStatusTool,\n listPentestsTool,\n getPentestTool,\n listAssetsTool,\n getAssetTool,\n ];\n\n for (const tool of tools) {\n const run = tool.handler(client) as (\n params: unknown,\n ) => Promise<ToolResult>;\n server.registerTool(\n tool.name,\n {\n title: tool.title,\n description: tool.description,\n inputSchema: tool.inputSchema.shape,\n },\n async (params: unknown) => {\n try {\n const { markdown, structured } = await run(params);\n return {\n content: [{ type: \"text\" as const, text: markdown }],\n structuredContent: { markdown, ...toObject(structured) },\n };\n } catch (error) {\n return {\n isError: true,\n content: [{ type: \"text\" as const, text: toErrorMessage(error) }],\n };\n }\n },\n );\n }\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(\n `odin-mcp ${PACKAGE_VERSION} connected (baseUrl=${baseUrl}, tools=${tools.length}).`,\n );\n}\n\nfunction toObject(value: unknown): Record<string, unknown> {\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return { data: value };\n}\n\nfunction toErrorMessage(error: unknown): string {\n if (error instanceof OdinApiError) return error.message;\n if (error instanceof Error) return `Unexpected error: ${error.message}`;\n return `Unexpected error: ${String(error)}`;\n}\n\nmain().catch((error) => {\n console.error(\"odin-mcp failed to start:\", error);\n process.exit(1);\n});\n","export class OdinApiError extends Error {\n readonly status: number;\n readonly path: string;\n readonly method: string;\n readonly retryAfterSeconds: number | null;\n readonly body: unknown;\n\n constructor(params: {\n status: number;\n path: string;\n method: string;\n retryAfterSeconds: number | null;\n body: unknown;\n message: string;\n }) {\n super(params.message);\n this.name = \"OdinApiError\";\n this.status = params.status;\n this.path = params.path;\n this.method = params.method;\n this.retryAfterSeconds = params.retryAfterSeconds;\n this.body = params.body;\n }\n}\n\nexport interface OdinClientOptions {\n apiKey: string;\n baseUrl?: string;\n fetchImpl?: typeof fetch;\n}\n\nexport const DEFAULT_ODIN_BASE_URL = \"https://odin.borghq.io\";\n\nconst WRITE_METHODS = new Set([\"PATCH\", \"POST\", \"PUT\", \"DELETE\"]);\n\nexport class OdinClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: OdinClientOptions) {\n if (!options.apiKey) {\n throw new Error(\"OdinClient requires an apiKey\");\n }\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_ODIN_BASE_URL).replace(\n /\\/+$/,\n \"\",\n );\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n async get<T>(\n path: string,\n searchParams?: Record<string, string | number | undefined>,\n ): Promise<T> {\n const url = this.buildUrl(path, searchParams);\n return this.request<T>(\"GET\", url, path);\n }\n\n async patch<T>(path: string, body: unknown): Promise<T> {\n const url = this.buildUrl(path);\n return this.request<T>(\"PATCH\", url, path, body);\n }\n\n async post<T>(path: string, body: unknown): Promise<T> {\n const url = this.buildUrl(path);\n return this.request<T>(\"POST\", url, path, body);\n }\n\n private buildUrl(\n path: string,\n searchParams?: Record<string, string | number | undefined>,\n ): string {\n const url = new URL(path.startsWith(\"/\") ? path : `/${path}`, this.baseUrl);\n if (searchParams) {\n for (const [key, value] of Object.entries(searchParams)) {\n if (value === undefined || value === \"\") continue;\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n }\n\n private async request<T>(\n method: string,\n url: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const response = await this.fetchImpl(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n const rawText = await response.text();\n const parsed = rawText ? safeJsonParse(rawText) : null;\n\n if (!response.ok) {\n const retryAfterSeconds = parseRetryAfter(\n response.headers.get(\"Retry-After\"),\n );\n throw new OdinApiError({\n status: response.status,\n path,\n method,\n retryAfterSeconds,\n body: parsed ?? rawText,\n message: formatErrorMessage({\n status: response.status,\n method,\n path,\n retryAfterSeconds,\n parsed,\n rawText,\n }),\n });\n }\n\n return parsed as T;\n }\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction parseRetryAfter(header: string | null): number | null {\n if (!header) return null;\n const trimmed = header.trim();\n if (/^\\d+$/.test(trimmed)) return Number.parseInt(trimmed, 10);\n const dateMs = Date.parse(trimmed);\n if (Number.isNaN(dateMs)) return null;\n const seconds = Math.ceil((dateMs - Date.now()) / 1000);\n return seconds > 0 ? seconds : null;\n}\n\ninterface FormatErrorInput {\n status: number;\n method: string;\n path: string;\n retryAfterSeconds: number | null;\n parsed: unknown;\n rawText: string;\n}\n\nfunction formatErrorMessage(input: FormatErrorInput): string {\n const serverMessage = extractServerMessage(input.parsed);\n const actionable = actionableMessage(input);\n if (actionable) {\n return serverMessage\n ? `${actionable} (server: ${serverMessage})`\n : actionable;\n }\n const suffix = serverMessage\n ? ` — ${serverMessage}`\n : input.rawText\n ? ` — ${input.rawText.slice(0, 200)}`\n : \"\";\n return `Odin API ${input.status} on ${input.path}${suffix}.`;\n}\n\nfunction actionableMessage(input: FormatErrorInput): string | null {\n switch (input.status) {\n case 401:\n return \"ODIN_API_KEY is not set or invalid. Create one at Management > API Keys in the Odin dashboard.\";\n case 403:\n if (WRITE_METHODS.has(input.method)) {\n return \"API key does not have write permission. Create a new key with 'Read & Write' permissions.\";\n }\n return null;\n case 404:\n if (isFindingDetailPath(input.path)) {\n return \"Finding not found. Check the finding ID is correct.\";\n }\n return null;\n case 429: {\n const n = input.retryAfterSeconds;\n return n\n ? `Rate limit exceeded. Try again in ${n} seconds.`\n : \"Rate limit exceeded. Try again shortly.\";\n }\n default:\n return null;\n }\n}\n\nfunction isFindingDetailPath(path: string): boolean {\n // /api/findings/{id} or /api/findings/{id}/status\n return /^\\/api\\/findings\\/[^/]+(\\/.*)?$/.test(path);\n}\n\nfunction extractServerMessage(parsed: unknown): string | null {\n if (parsed && typeof parsed === \"object\" && \"error\" in parsed) {\n const err = (parsed as { error: unknown }).error;\n if (typeof err === \"string\") return err;\n }\n return null;\n}\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatAssetDetail } from \"../formatters/markdown.js\";\nimport type { AssetDetailResponse } from \"../types.js\";\n\nexport const getAssetInput = z.object({\n assetId: z.string().min(1),\n});\n\nexport const getAssetTool = {\n name: \"get_asset\",\n title: \"Get asset\",\n description:\n \"Fetch a single attack-surface asset by ID, including the findings that reference it in their latest revision.\",\n inputSchema: getAssetInput,\n handler:\n (client: OdinClient) => async (params: z.infer<typeof getAssetInput>) => {\n const data = await client.get<AssetDetailResponse>(\n `/api/assets/${encodeURIComponent(params.assetId)}`,\n );\n return {\n markdown: formatAssetDetail(data),\n structured: { asset: data.asset, findings: data.findings },\n };\n },\n};\n","import type {\n Asset,\n AssetDetailResponse,\n AssetsListResponse,\n FindingDetailResponse,\n FindingSummary,\n FindingsListResponse,\n Pentest,\n PentestDetailResponse,\n PentestListResponse,\n} from \"../types.js\";\n\nexport function formatFindingsList(data: FindingsListResponse): string {\n const { findings, stats, pagination } = data;\n if (findings.length === 0) {\n return \"No findings match the supplied filters.\";\n }\n\n const rows = findings.map((f) => {\n const severity = `${f.severity.label} (CVSS ${f.severity.cvss.toFixed(1)})`;\n return `| ${f.findingId} | ${escapePipes(f.title)} | ${severity} | ${f.status} | ${formatDate(f.createdAt)} |`;\n });\n\n return [\n `Showing ${findings.length} of ${stats.total} finding(s). Offset ${pagination.offset}, limit ${pagination.limit}. hasMore=${pagination.hasMore}.`,\n \"\",\n \"| Finding ID | Title | Severity | Status | Created |\",\n \"| --- | --- | --- | --- | --- |\",\n ...rows,\n ].join(\"\\n\");\n}\n\nexport function formatFindingDetail(data: FindingDetailResponse): string {\n const { finding, revision } = data;\n const severity = `${finding.severity.label} (CVSS ${finding.severity.cvss.toFixed(1)})`;\n const header = [\n `# ${finding.title}`,\n \"\",\n `- **ID:** ${finding.findingId}`,\n `- **Severity:** ${severity}`,\n `- **Status:** ${finding.status}`,\n `- **Pentest:** ${finding.context?.pentestId ?? \"standalone\"}`,\n `- **Created:** ${formatDate(finding.createdAt)}`,\n `- **Updated:** ${formatDate(finding.updatedAt)}`,\n ];\n\n if (!revision) {\n return [...header, \"\", \"_No revision content available._\"].join(\"\\n\");\n }\n\n const c = revision.content;\n const sections: string[] = [];\n if (c.summaryMd) sections.push(\"## Summary\", c.summaryMd);\n if (c.descriptionMd) sections.push(\"## Description\", c.descriptionMd);\n if (c.businessImpactMd)\n sections.push(\"## Business Impact\", c.businessImpactMd);\n if (c.detailsMd) sections.push(\"## Details\", c.detailsMd);\n if (c.remediationMd) sections.push(\"## Remediation\", c.remediationMd);\n if (c.affectedAssets?.length) {\n sections.push(\n \"## Affected Assets\",\n c.affectedAssets.map((a) => `- ${a.display} (${a.assetId})`).join(\"\\n\"),\n );\n }\n\n return [\n ...header,\n \"\",\n `_Revision ${revision.revisionNumber}_`,\n \"\",\n ...sections,\n ].join(\"\\n\");\n}\n\nexport function formatFindingStatusUpdate(finding: FindingSummary): string {\n return [\n `Updated finding **${finding.findingId}** status to **${finding.status}**.`,\n `Title: ${finding.title}`,\n ].join(\"\\n\");\n}\n\nexport function formatPentestsList(data: PentestListResponse): string {\n if (data.pentests.length === 0) return \"No pentests found.\";\n const rows = data.pentests.map(formatPentestRow);\n return [\n \"| Pentest ID | Name | Status | Start | End | Scope |\",\n \"| --- | --- | --- | --- | --- | --- |\",\n ...rows,\n ].join(\"\\n\");\n}\n\nexport function formatPentestDetail(data: PentestDetailResponse): string {\n const p = data.pentest;\n const end = p.endAt ? formatDate(p.endAt) : \"open\";\n const scope = p.scope.length\n ? p.scope.map((s) => `- ${s}`).join(\"\\n\")\n : \"_No scope defined._\";\n return [\n `# ${p.name}`,\n \"\",\n `- **ID:** ${p.pentestId}`,\n `- **Status:** ${p.status}`,\n `- **Start:** ${formatDate(p.startAt)}`,\n `- **End:** ${end}`,\n `- **Created:** ${formatDate(p.createdAt)}`,\n \"\",\n \"## Scope\",\n \"\",\n scope,\n ].join(\"\\n\");\n}\n\nfunction formatPentestRow(p: Pentest): string {\n const end = p.endAt ? formatDate(p.endAt) : \"open\";\n const scope =\n p.scope.slice(0, 3).join(\", \") + (p.scope.length > 3 ? \", …\" : \"\");\n return `| ${p.pentestId} | ${escapePipes(p.name)} | ${p.status} | ${formatDate(p.startAt)} | ${end} | ${escapePipes(scope)} |`;\n}\n\nexport function formatAssetsList(data: AssetsListResponse): string {\n const { assets, stats, pagination } = data;\n if (assets.length === 0) return \"No assets match the supplied filters.\";\n const rows = assets.map(formatAssetRow);\n return [\n `Showing ${assets.length} of ${stats.total} asset(s). Offset ${pagination.offset}, limit ${pagination.limit}. hasMore=${pagination.hasMore}.`,\n \"\",\n \"| Asset ID | Type | Value | Source | Status | Last seen |\",\n \"| --- | --- | --- | --- | --- | --- |\",\n ...rows,\n ].join(\"\\n\");\n}\n\nexport function formatAssetDetail(data: AssetDetailResponse): string {\n const { asset, findings } = data;\n const header = [\n `# ${asset.value}`,\n \"\",\n `- **ID:** ${asset.assetId}`,\n `- **Type:** ${asset.type}`,\n `- **Source:** ${asset.source}`,\n `- **Status:** ${asset.status}`,\n `- **Last seen:** ${formatDate(asset.lastSeenAt)}`,\n `- **Created:** ${formatDate(asset.createdAt)}`,\n ];\n\n if (findings.length === 0) {\n return [...header, \"\", \"_No findings reference this asset._\"].join(\"\\n\");\n }\n\n const rows = findings.map((f) => {\n const severity = `${f.severity.label} (CVSS ${f.severity.cvss.toFixed(1)})`;\n return `| ${f.findingId} | ${escapePipes(f.title)} | ${severity} | ${f.status} | ${formatDate(f.createdAt)} |`;\n });\n\n return [\n ...header,\n \"\",\n `## Findings (${findings.length})`,\n \"\",\n \"| Finding ID | Title | Severity | Status | Created |\",\n \"| --- | --- | --- | --- | --- |\",\n ...rows,\n ].join(\"\\n\");\n}\n\nfunction formatAssetRow(a: Asset): string {\n return `| ${a.assetId} | ${a.type} | ${escapePipes(a.value)} | ${a.source} | ${a.status} | ${formatDate(a.lastSeenAt)} |`;\n}\n\nfunction escapePipes(value: string): string {\n return value.replace(/\\|/g, \"\\\\|\");\n}\n\nfunction formatDate(epoch: number): string {\n if (!epoch) return \"—\";\n // Odin stores timestamps as milliseconds for documents and seconds for some fields\n // (pentest startAt/endAt are seconds). Heuristic: values < 10^12 are seconds.\n const ms = epoch < 1e12 ? epoch * 1000 : epoch;\n return new Date(ms).toISOString().slice(0, 10);\n}\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatFindingDetail } from \"../formatters/markdown.js\";\nimport type { FindingDetailResponse } from \"../types.js\";\n\nexport const getFindingInput = z.object({\n findingId: z.string().min(1),\n});\n\nexport const getFindingTool = {\n name: \"get_finding\",\n title: \"Get finding\",\n description:\n \"Fetch a single finding by ID, including the latest revision content (description, impact, details, remediation).\",\n inputSchema: getFindingInput,\n handler:\n (client: OdinClient) => async (params: z.infer<typeof getFindingInput>) => {\n const data = await client.get<FindingDetailResponse>(\n `/api/findings/${encodeURIComponent(params.findingId)}`,\n );\n return {\n markdown: formatFindingDetail(data),\n structured: {\n finding: data.finding,\n revision: data.revision,\n },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatPentestDetail } from \"../formatters/markdown.js\";\nimport type { PentestDetailResponse } from \"../types.js\";\n\nexport const getPentestInput = z.object({\n pentestId: z.string().min(1),\n});\n\nexport const getPentestTool = {\n name: \"get_pentest\",\n title: \"Get pentest\",\n description:\n \"Fetch a single pentest by ID, including status, scheduled window, and in-scope targets.\",\n inputSchema: getPentestInput,\n handler:\n (client: OdinClient) => async (params: z.infer<typeof getPentestInput>) => {\n const data = await client.get<PentestDetailResponse>(\n `/api/pentests/${encodeURIComponent(params.pentestId)}`,\n );\n return {\n markdown: formatPentestDetail(data),\n structured: { pentest: data.pentest },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatAssetsList } from \"../formatters/markdown.js\";\nimport type { AssetsListResponse } from \"../types.js\";\n\nexport const listAssetsInput = z.object({\n type: z.enum([\"DOMAIN\", \"SUBDOMAIN\", \"IP\", \"URL\", \"WILDCARD\"]).optional(),\n source: z.enum([\"MANUAL\", \"RECON\", \"IMPORT\", \"AGENT\"]).optional(),\n status: z\n .string()\n .optional()\n .describe(\n \"Dynamic asset status set by recon (e.g. ACTIVE, INACTIVE, TLS_ERROR).\",\n ),\n search: z.string().optional(),\n sortBy: z.enum([\"lastSeenAt\", \"createdAt\", \"value\"]).optional(),\n sortOrder: z.enum([\"asc\", \"desc\"]).optional(),\n limit: z.number().int().min(1).max(100).optional().default(50),\n offset: z.number().int().min(0).optional().default(0),\n});\n\nexport const listAssetsTool = {\n name: \"list_assets\",\n title: \"List assets\",\n description:\n \"List attack-surface assets for the organisation. Supports filtering by type, source, status, and free-text search.\",\n inputSchema: listAssetsInput,\n handler:\n (client: OdinClient) => async (params: z.infer<typeof listAssetsInput>) => {\n const data = await client.get<AssetsListResponse>(\"/api/assets\", params);\n return {\n markdown: formatAssetsList(data),\n structured: {\n assets: data.assets,\n stats: data.stats,\n pagination: data.pagination,\n },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatFindingsList } from \"../formatters/markdown.js\";\nimport type { FindingsListResponse } from \"../types.js\";\n\nexport const listFindingsInput = z.object({\n pentestId: z.string().optional().describe(\"Filter to a single pentest.\"),\n assetId: z\n .string()\n .optional()\n .describe(\n \"Filter to findings whose latest revision references this asset.\",\n ),\n severity: z\n .enum([\"CRITICAL\", \"HIGH\", \"MEDIUM\", \"LOW\", \"INFORMATIONAL\"])\n .optional(),\n status: z\n .enum([\n \"DRAFT\",\n \"IN_REVIEW\",\n \"REPORTED\",\n \"MITIGATING\",\n \"OPEN_FOR_RETEST\",\n \"RESOLVED\",\n \"NOT_REPRODUCIBLE\",\n \"ACCEPTED_RISK\",\n ])\n .optional(),\n search: z.string().optional().describe(\"Free-text search on finding titles.\"),\n sortBy: z.enum([\"severity\", \"createdAt\"]).optional(),\n sortOrder: z.enum([\"asc\", \"desc\"]).optional(),\n limit: z.number().int().min(1).max(200).optional().default(50),\n offset: z.number().int().min(0).optional().default(0),\n});\n\nexport const listFindingsTool = {\n name: \"list_findings\",\n title: \"List findings\",\n description:\n \"List security findings for the organisation. Supports filtering by pentest, severity, status, and free-text search.\",\n inputSchema: listFindingsInput,\n handler:\n (client: OdinClient) =>\n async (params: z.infer<typeof listFindingsInput>) => {\n const data = await client.get<FindingsListResponse>(\n \"/api/findings\",\n params,\n );\n return {\n markdown: formatFindingsList(data),\n structured: {\n findings: data.findings,\n stats: data.stats,\n pagination: data.pagination,\n },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatPentestsList } from \"../formatters/markdown.js\";\nimport type { PentestListResponse } from \"../types.js\";\n\nexport const listPentestsInput = z.object({});\n\nexport const listPentestsTool = {\n name: \"list_pentests\",\n title: \"List pentests\",\n description:\n \"List all pentests for the organisation, including status, scheduled window, and in-scope targets.\",\n inputSchema: listPentestsInput,\n handler: (client: OdinClient) => async () => {\n const data = await client.get<PentestListResponse>(\"/api/pentests\");\n return {\n markdown: formatPentestsList(data),\n structured: { pentests: data.pentests },\n };\n },\n};\n","import { z } from \"zod\";\nimport type { OdinClient } from \"../client.js\";\nimport { formatFindingStatusUpdate } from \"../formatters/markdown.js\";\nimport type { FindingStatusUpdateResponse } from \"../types.js\";\n\nexport const updateFindingStatusInput = z.object({\n findingId: z.string().min(1),\n status: z\n .enum([\"MITIGATING\", \"OPEN_FOR_RETEST\", \"ACKNOWLEDGED\"])\n .describe(\n \"Target status. Only MITIGATING, OPEN_FOR_RETEST, and ACKNOWLEDGED are client-writable; transitions are only allowed from REPORTED, MITIGATING, or OPEN_FOR_RETEST. ACKNOWLEDGED marks the finding as accepted-risk and is terminal.\",\n ),\n});\n\nexport const updateFindingStatusTool = {\n name: \"update_finding_status\",\n title: \"Update finding status\",\n description:\n \"Change a finding's status. Requires an API key with write permission and member role. Only MITIGATING, OPEN_FOR_RETEST, and ACKNOWLEDGED are accepted.\",\n inputSchema: updateFindingStatusInput,\n handler:\n (client: OdinClient) =>\n async (params: z.infer<typeof updateFindingStatusInput>) => {\n const data = await client.patch<FindingStatusUpdateResponse>(\n `/api/findings/${encodeURIComponent(params.findingId)}/status`,\n { status: params.status },\n );\n return {\n markdown: formatFindingStatusUpdate(data.finding),\n structured: { finding: data.finding },\n };\n },\n};\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z","z","z","z"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { DEFAULT_ODIN_BASE_URL, OdinClient } from \"./client.js\";\nimport { createOdinMcpServer, PACKAGE_VERSION } from \"./server.js\";\n\nasync function main() {\n const apiKey = process.env.ODIN_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"ODIN_API_KEY is required. Create a key under Management > API Keys.\",\n );\n }\n const baseUrl = process.env.ODIN_BASE_URL ?? DEFAULT_ODIN_BASE_URL;\n const client = new OdinClient({\n apiKey,\n baseUrl,\n vercelProtectionBypassSecret:\n process.env.ODIN_VERCEL_PROTECTION_BYPASS_SECRET,\n });\n const server = createOdinMcpServer(client, { appUrl: baseUrl });\n await server.connect(new StdioServerTransport());\n console.error(`odin-mcp ${PACKAGE_VERSION} connected over stdio.`);\n}\n\nmain().catch((error) => {\n console.error(\"odin-mcp failed to start:\", error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,4BAA4B;AAIrC,eAAe,OAAO;AACpB,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,UAAU,QAAQ,IAAI,iBAAiB;AAC7C,QAAM,SAAS,IAAI,WAAW;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,8BACE,QAAQ,IAAI;AAAA,EAChB,CAAC;AACD,QAAM,SAAS,oBAAoB,QAAQ,EAAE,QAAQ,QAAQ,CAAC;AAC9D,QAAM,OAAO,QAAQ,IAAI,qBAAqB,CAAC;AAC/C,UAAQ,MAAM,YAAY,eAAe,wBAAwB;AACnE;AAEA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,MAAM,6BAA6B,KAAK;AAChD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
@@ -0,0 +1,11 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { OdinClient } from './client.js';
3
+
4
+ declare const PACKAGE_VERSION: string;
5
+ interface OdinMcpServerOptions {
6
+ /** Public Odin origin for links in guidance. Defaults to production. */
7
+ appUrl?: string;
8
+ }
9
+ declare function createOdinMcpServer(client: OdinClient, options?: OdinMcpServerOptions): McpServer;
10
+
11
+ export { type OdinMcpServerOptions, PACKAGE_VERSION, createOdinMcpServer };