@systemdox/mcp-server 0.1.11

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.
@@ -0,0 +1,575 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/language.ts
4
+ var EXTENSION_MAP = {
5
+ ".py": { language: "python", categories: ["api", "backend", "data-access"] },
6
+ ".ts": { language: "typescript", categories: ["frontend", "api"] },
7
+ ".tsx": { language: "typescript", categories: ["frontend"] },
8
+ ".js": { language: "javascript", categories: ["frontend"] },
9
+ ".jsx": { language: "javascript", categories: ["frontend"] },
10
+ ".yaml": {
11
+ language: "cloudformation",
12
+ categories: ["infrastructure", "architecture"]
13
+ },
14
+ ".yml": {
15
+ language: "cloudformation",
16
+ categories: ["infrastructure", "architecture"]
17
+ },
18
+ ".json": { language: "json", categories: ["configuration"] },
19
+ ".css": { language: "css", categories: ["frontend"] },
20
+ ".html": { language: "html", categories: ["frontend"] },
21
+ ".sql": { language: "sql", categories: ["data-access"] },
22
+ ".sh": { language: "shell", categories: ["infrastructure"] },
23
+ ".bash": { language: "shell", categories: ["infrastructure"] },
24
+ ".tf": { language: "terraform", categories: ["infrastructure"] },
25
+ ".md": { language: "markdown", categories: ["documentation"] }
26
+ };
27
+ var PATH_CATEGORY_RULES = [
28
+ { pattern: /backend\/functions\//, categories: ["api", "backend"] },
29
+ { pattern: /backend\/layers\//, categories: ["backend"] },
30
+ { pattern: /backend\//, categories: ["backend"] },
31
+ { pattern: /apps\/app\//, categories: ["frontend"] },
32
+ { pattern: /apps\/website\//, categories: ["frontend"] },
33
+ { pattern: /apps\/portal\//, categories: ["frontend"] },
34
+ { pattern: /packages\/ui\//, categories: ["frontend"] },
35
+ { pattern: /infra\//, categories: ["infrastructure"] },
36
+ { pattern: /\.github\//, categories: ["infrastructure"] },
37
+ { pattern: /tests?\//, categories: ["testing"] },
38
+ { pattern: /spec/, categories: ["testing"] },
39
+ { pattern: /docs\/adr/, categories: ["architecture"] },
40
+ { pattern: /template\.yaml$/, categories: ["infrastructure", "architecture"] }
41
+ ];
42
+ function detectLanguage(filePath) {
43
+ const ext = filePath.slice(filePath.lastIndexOf("."));
44
+ return EXTENSION_MAP[ext];
45
+ }
46
+ function detectCategories(filePaths) {
47
+ const categories = /* @__PURE__ */ new Set();
48
+ for (const filePath of filePaths) {
49
+ const langInfo = detectLanguage(filePath);
50
+ if (langInfo) {
51
+ for (const cat of langInfo.categories) {
52
+ categories.add(cat);
53
+ }
54
+ }
55
+ for (const rule of PATH_CATEGORY_RULES) {
56
+ if (rule.pattern.test(filePath)) {
57
+ for (const cat of rule.categories) {
58
+ categories.add(cat);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ return Array.from(categories);
64
+ }
65
+ function detectLanguages(filePaths) {
66
+ const languages = /* @__PURE__ */ new Set();
67
+ for (const filePath of filePaths) {
68
+ const info = detectLanguage(filePath);
69
+ if (info) languages.add(info.language);
70
+ }
71
+ return Array.from(languages);
72
+ }
73
+
74
+ // src/lib/scoring.ts
75
+ var WEIGHT_PATH = 0.4;
76
+ var WEIGHT_KEYWORD = 0.3;
77
+ var WEIGHT_RECENCY = 0.15;
78
+ var WEIGHT_SEVERITY = 0.15;
79
+ var SEVERITY_SCORES = {
80
+ critical: 1,
81
+ high: 0.75,
82
+ medium: 0.5,
83
+ low: 0.25
84
+ };
85
+ var CHARS_PER_TOKEN = 4;
86
+ function estimateTokens(text) {
87
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
88
+ }
89
+ function truncateToTokens(text, maxTokens) {
90
+ const maxChars = Math.max(0, maxTokens) * CHARS_PER_TOKEN;
91
+ if (text.length <= maxChars) return { text, truncated: false };
92
+ const head = text.slice(0, maxChars);
93
+ const floor = Math.floor(maxChars * 0.75);
94
+ const paragraph = head.lastIndexOf("\n\n");
95
+ const line = head.lastIndexOf("\n");
96
+ let cut = maxChars;
97
+ if (paragraph >= floor) cut = paragraph;
98
+ else if (line >= floor) cut = line;
99
+ return { text: head.slice(0, cut).trimEnd(), truncated: true };
100
+ }
101
+ function scorePathMatch(item, languages, categories) {
102
+ if (languages.length === 0 && categories.length === 0) return 0.5;
103
+ let score = 0;
104
+ const itemLang = item.language?.toLowerCase() ?? "";
105
+ const itemCat = item.category?.toLowerCase() ?? "";
106
+ if (itemLang && languages.some((l) => itemLang.includes(l))) {
107
+ score += 0.6;
108
+ }
109
+ if (itemCat && categories.some((c) => itemCat.includes(c))) {
110
+ score += 0.4;
111
+ }
112
+ return Math.min(score, 1);
113
+ }
114
+ function scoreKeywordMatch(item, keywords) {
115
+ if (keywords.length === 0) return 0;
116
+ const searchable = `${item.title} ${item.content}`.toLowerCase();
117
+ let matches = 0;
118
+ for (const kw of keywords) {
119
+ if (searchable.includes(kw)) matches++;
120
+ }
121
+ return Math.min(matches / keywords.length, 1);
122
+ }
123
+ function scoreRecency(item) {
124
+ if (!item.updatedAt) return 0.5;
125
+ const now = Date.now();
126
+ const updated = new Date(item.updatedAt).getTime();
127
+ const daysSince = (now - updated) / (1e3 * 60 * 60 * 24);
128
+ if (daysSince <= 7) return 1;
129
+ if (daysSince <= 30) return 0.75;
130
+ if (daysSince <= 90) return 0.5;
131
+ return 0.25;
132
+ }
133
+ function scoreSeverity(item) {
134
+ if (!item.severity) return 0.5;
135
+ return SEVERITY_SCORES[item.severity] ?? 0.5;
136
+ }
137
+ function extractKeywords(text) {
138
+ const stopWords = /* @__PURE__ */ new Set([
139
+ "the",
140
+ "a",
141
+ "an",
142
+ "is",
143
+ "are",
144
+ "was",
145
+ "were",
146
+ "be",
147
+ "been",
148
+ "being",
149
+ "have",
150
+ "has",
151
+ "had",
152
+ "do",
153
+ "does",
154
+ "did",
155
+ "will",
156
+ "would",
157
+ "could",
158
+ "should",
159
+ "may",
160
+ "might",
161
+ "can",
162
+ "shall",
163
+ "to",
164
+ "of",
165
+ "in",
166
+ "for",
167
+ "on",
168
+ "with",
169
+ "at",
170
+ "by",
171
+ "from",
172
+ "as",
173
+ "into",
174
+ "about",
175
+ "between",
176
+ "through",
177
+ "after",
178
+ "before",
179
+ "during",
180
+ "without",
181
+ "this",
182
+ "that",
183
+ "these",
184
+ "those",
185
+ "it",
186
+ "its",
187
+ "and",
188
+ "or",
189
+ "but",
190
+ "not",
191
+ "no",
192
+ "all",
193
+ "each",
194
+ "every",
195
+ "both",
196
+ "few",
197
+ "more",
198
+ "most",
199
+ "other",
200
+ "some",
201
+ "such",
202
+ "than",
203
+ "too",
204
+ "very",
205
+ "just",
206
+ "also",
207
+ "only",
208
+ "new",
209
+ "add",
210
+ "update",
211
+ "fix",
212
+ "change",
213
+ "make",
214
+ "use",
215
+ "get",
216
+ "set",
217
+ "need",
218
+ "want",
219
+ "implement",
220
+ "create"
221
+ ]);
222
+ return text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
223
+ }
224
+ function scoreItems(items, taskDescription, filePaths) {
225
+ const languages = detectLanguages(filePaths);
226
+ const categories = detectCategories(filePaths);
227
+ const keywords = extractKeywords(taskDescription);
228
+ return items.map((item) => {
229
+ const pathScore = scorePathMatch(item, languages, categories);
230
+ const keywordScore = scoreKeywordMatch(item, keywords);
231
+ const recencyScore = scoreRecency(item);
232
+ const severityScore = scoreSeverity(item);
233
+ const score = pathScore * WEIGHT_PATH + keywordScore * WEIGHT_KEYWORD + recencyScore * WEIGHT_RECENCY + severityScore * WEIGHT_SEVERITY;
234
+ return {
235
+ type: item.type,
236
+ id: item.id,
237
+ title: item.title,
238
+ content: item.content,
239
+ score,
240
+ tokenEstimate: estimateTokens(item.content)
241
+ };
242
+ }).sort((a, b) => b.score - a.score);
243
+ }
244
+ function selectWithinBudget(items, maxTokens) {
245
+ const selected = [];
246
+ let tokensUsed = 0;
247
+ for (const item of items) {
248
+ if (tokensUsed + item.tokenEstimate > maxTokens) continue;
249
+ selected.push(item);
250
+ tokensUsed += item.tokenEstimate;
251
+ }
252
+ return { selected, tokensUsed, totalItems: items.length };
253
+ }
254
+
255
+ // src/lib/context-builder.ts
256
+ var DEFAULT_BUDGET = 4e3;
257
+ var RESERVE_TOKENS = 200;
258
+ var GUARDRAIL_SHARE = 0.35;
259
+ var MIN_BODY_TOKENS = 150;
260
+ var MAX_HYDRATED = 8;
261
+ var MAX_TAIL = 10;
262
+ var TAIL_SHARE = 0.15;
263
+ var TRUNCATION_NOTICE_TOKENS = 30;
264
+ function formatGuardrail(g) {
265
+ let text = `## ${g.title} [${g.severity}]
266
+
267
+ ${g.description}`;
268
+ if (g.pattern) text += `
269
+
270
+ **Bad:** \`\`\`
271
+ ${g.pattern}
272
+ \`\`\``;
273
+ if (g.fix) text += `
274
+
275
+ **Good:** \`\`\`
276
+ ${g.fix}
277
+ \`\`\``;
278
+ return text;
279
+ }
280
+ function formatSpecHeadline(s) {
281
+ return `### ${s.name}
282
+ \`spec_id: ${s.specId}\` \xB7 ${s.status} \xB7 ${s.requirementCount} requirements`;
283
+ }
284
+ function formatDocHeadline(d) {
285
+ return `### ${d.title}
286
+ \`doc_id: ${d.id}\` \xB7 \`${d.repo_full_name}/${d.path}\` \xB7 ${d.doc_type}`;
287
+ }
288
+ function formatStack(entries) {
289
+ return entries.map(
290
+ (e) => `- ${e.name}${e.version ? ` v${e.version}` : ""}${e.notes ? ` \u2014 ${e.notes}` : ""}`
291
+ ).join("\n");
292
+ }
293
+ function formatRules(rules) {
294
+ const byCategory = /* @__PURE__ */ new Map();
295
+ for (const r of rules) {
296
+ const cat = r.category || "other";
297
+ if (!byCategory.has(cat)) byCategory.set(cat, []);
298
+ byCategory.get(cat).push(r);
299
+ }
300
+ let text = "";
301
+ for (const [cat, catRules] of byCategory) {
302
+ text += `### ${cat.charAt(0).toUpperCase() + cat.slice(1)}
303
+ `;
304
+ for (const r of catRules) {
305
+ text += `- ${r.text}
306
+ `;
307
+ }
308
+ text += "\n";
309
+ }
310
+ return `## Project Rules (${rules.length})
311
+
312
+ ${text.trim()}`;
313
+ }
314
+ async function fetchSpecBody(api, specId) {
315
+ const spec = await api.get(
316
+ `/v1/specs/${encodeURIComponent(specId)}`
317
+ );
318
+ const parts = [];
319
+ if (spec.proposalContent) {
320
+ parts.push(`**Proposal**
321
+
322
+ ${spec.proposalContent}`);
323
+ }
324
+ for (const r of spec.requirements ?? []) {
325
+ parts.push(`**${r.name}**
326
+
327
+ ${r.content}`);
328
+ }
329
+ if (spec.taskContent) {
330
+ parts.push(`**Tasks**
331
+
332
+ ${spec.taskContent}`);
333
+ }
334
+ const body = parts.join("\n\n").trim();
335
+ if (!body) throw new Error("spec has no content");
336
+ return body;
337
+ }
338
+ async function fetchDocBody(api, docId) {
339
+ const doc = await api.get(
340
+ `/v1/docs/${encodeURIComponent(docId)}/content`
341
+ );
342
+ if (doc.error) throw new Error(doc.error);
343
+ const body = (doc.content ?? "").trim();
344
+ if (!body) throw new Error("document is empty");
345
+ return body;
346
+ }
347
+ async function hydrate(api, item, meta) {
348
+ try {
349
+ const body = meta.type === "spec" ? await fetchSpecBody(api, meta.id) : await fetchDocBody(api, meta.id);
350
+ return { item, meta, body };
351
+ } catch (err) {
352
+ return {
353
+ item,
354
+ meta,
355
+ error: err instanceof Error ? err.message : String(err)
356
+ };
357
+ }
358
+ }
359
+ async function buildContext(api, { taskDescription, filePaths = [], maxTokens }) {
360
+ const budget = maxTokens ?? DEFAULT_BUDGET;
361
+ const [guardrails, specs, docs, stack, rules] = await Promise.all([
362
+ api.get("/v1/guardrails").catch(() => []),
363
+ api.get("/v1/specs").catch(() => []),
364
+ api.get("/v1/docs").catch(() => ({ items: [] })),
365
+ api.get("/v1/ai/settings/stack").catch(() => []),
366
+ api.get("/v1/standards/rules").catch(() => [])
367
+ ]);
368
+ const refs = /* @__PURE__ */ new Map();
369
+ const key = (type, id) => `${type}:${id}`;
370
+ const scorable = [
371
+ ...guardrails.map((g) => ({
372
+ type: "guardrail",
373
+ id: g.id,
374
+ title: g.title,
375
+ content: formatGuardrail(g),
376
+ category: g.category,
377
+ severity: g.severity,
378
+ updatedAt: g.updatedAt
379
+ })),
380
+ ...specs.filter((s) => s.status !== "archived").map((s) => {
381
+ refs.set(key("spec", s.specId), {
382
+ type: "spec",
383
+ id: s.specId,
384
+ title: s.name,
385
+ listLine: `- **${s.name}** \u2014 \`spec_id: ${s.specId}\` (${s.status}, ${s.requirementCount} requirements)`,
386
+ fetchHint: `get_specs(spec_id="${s.specId}")`
387
+ });
388
+ return {
389
+ type: "spec",
390
+ id: s.specId,
391
+ title: s.name,
392
+ content: formatSpecHeadline(s),
393
+ updatedAt: s.updatedAt
394
+ };
395
+ }),
396
+ ...docs.items.map((d) => {
397
+ refs.set(key("doc", d.id), {
398
+ type: "doc",
399
+ id: d.id,
400
+ title: d.title,
401
+ listLine: `- **${d.title}** \u2014 \`doc_id: ${d.id}\` (\`${d.repo_full_name}/${d.path}\`)`,
402
+ fetchHint: `get_docs(doc_id="${d.id}")`
403
+ });
404
+ return {
405
+ type: "doc",
406
+ id: d.id,
407
+ title: d.title,
408
+ content: formatDocHeadline(d),
409
+ category: d.doc_type === "adr" ? "architecture" : void 0,
410
+ updatedAt: d.synced_at
411
+ };
412
+ })
413
+ ];
414
+ const ranked = scoreItems(scorable, taskDescription, filePaths);
415
+ const sections = [];
416
+ let overhead = 0;
417
+ if (stack.length > 0) {
418
+ const section = `## Tech Stack
419
+
420
+ ${formatStack(stack)}`;
421
+ sections.push(section);
422
+ overhead += estimateTokens(section);
423
+ }
424
+ if (rules.length > 0) {
425
+ const section = formatRules(rules);
426
+ sections.push(section);
427
+ overhead += estimateTokens(section);
428
+ }
429
+ let remaining = budget - RESERVE_TOKENS - overhead;
430
+ const rankedGuardrails = ranked.filter((i) => i.type === "guardrail");
431
+ const { selected: selectedGuardrails, tokensUsed: guardrailTokens } = selectWithinBudget(
432
+ rankedGuardrails,
433
+ Math.max(0, Math.floor(remaining * GUARDRAIL_SHARE))
434
+ );
435
+ remaining -= guardrailTokens;
436
+ const rankedRefs = ranked.filter(
437
+ (i) => i.type === "spec" || i.type === "doc"
438
+ );
439
+ const avgHeadline = rankedRefs.length ? Math.ceil(
440
+ rankedRefs.slice(0, MAX_HYDRATED).reduce((sum, i) => sum + i.tokenEstimate, 0) / Math.min(rankedRefs.length, MAX_HYDRATED)
441
+ ) : 0;
442
+ const affordable = Math.floor(
443
+ Math.max(0, remaining) / (MIN_BODY_TOKENS + avgHeadline)
444
+ );
445
+ const shortlist = rankedRefs.slice(
446
+ 0,
447
+ Math.max(0, Math.min(MAX_HYDRATED, affordable))
448
+ );
449
+ for (const item of shortlist) {
450
+ remaining -= item.tokenEstimate;
451
+ }
452
+ remaining = Math.max(0, remaining);
453
+ const hydrated = await Promise.all(
454
+ shortlist.map(
455
+ (item) => hydrate(api, item, refs.get(key(item.type, item.id)))
456
+ )
457
+ );
458
+ const tailCandidates = rankedRefs.slice(shortlist.length, shortlist.length + MAX_TAIL).map((i) => refs.get(key(i.type, i.id)));
459
+ const tailCost = tailCandidates.reduce(
460
+ (sum, m) => sum + estimateTokens(m.listLine),
461
+ 0
462
+ );
463
+ const tailReserve = Math.min(
464
+ tailCost,
465
+ Math.floor(remaining * TAIL_SHARE)
466
+ );
467
+ const bodyBudgetStart = Math.max(0, remaining - tailReserve);
468
+ let bodyBudget = bodyBudgetStart;
469
+ let itemsLeft = hydrated.length;
470
+ let itemsWithBody = 0;
471
+ let bodyFailures = 0;
472
+ const blocks = [];
473
+ for (const h of hydrated) {
474
+ itemsLeft--;
475
+ const emit = (block) => {
476
+ bodyBudget -= Math.max(
477
+ 0,
478
+ estimateTokens(block) - h.item.tokenEstimate
479
+ );
480
+ blocks.push(block);
481
+ };
482
+ if (h.error !== void 0) {
483
+ bodyFailures++;
484
+ emit(
485
+ `${h.item.content}
486
+
487
+ _Body unavailable (${h.error}) \u2014 retry with \`${h.meta.fetchHint}\`._`
488
+ );
489
+ continue;
490
+ }
491
+ const available = Math.max(0, bodyBudget);
492
+ const allowance = Math.min(
493
+ Math.max(MIN_BODY_TOKENS, Math.floor(available / (itemsLeft + 1))),
494
+ available
495
+ );
496
+ if (allowance < MIN_BODY_TOKENS) {
497
+ emit(
498
+ `${h.item.content}
499
+
500
+ _Body omitted (token budget exhausted) \u2014 fetch with \`${h.meta.fetchHint}\`._`
501
+ );
502
+ continue;
503
+ }
504
+ const { text: body2, truncated } = truncateToTokens(
505
+ h.body,
506
+ allowance - TRUNCATION_NOTICE_TOKENS
507
+ );
508
+ itemsWithBody++;
509
+ emit(
510
+ `${h.item.content}
511
+
512
+ ${body2}` + (truncated ? `
513
+
514
+ _[truncated to fit the token budget \u2014 full text via \`${h.meta.fetchHint}\`]_` : "")
515
+ );
516
+ }
517
+ remaining = Math.max(0, remaining - (bodyBudgetStart - bodyBudget));
518
+ if (selectedGuardrails.length > 0) {
519
+ sections.push(
520
+ `## Checks (${selectedGuardrails.length})
521
+
522
+ ` + selectedGuardrails.map((i) => i.content).join("\n\n---\n\n")
523
+ );
524
+ }
525
+ if (blocks.length > 0) {
526
+ sections.push(
527
+ `## Relevant Specs & Docs (${blocks.length})
528
+
529
+ _Full text of the highest-ranked items. Every item carries the ID to fetch it in full._
530
+
531
+ ` + blocks.join("\n\n---\n\n")
532
+ );
533
+ }
534
+ const tailLines = [];
535
+ for (const meta of tailCandidates) {
536
+ const cost = estimateTokens(meta.listLine);
537
+ if (cost > remaining) break;
538
+ tailLines.push(meta.listLine);
539
+ remaining -= cost;
540
+ }
541
+ if (tailLines.length > 0) {
542
+ sections.push(
543
+ `## Also Indexed (${tailLines.length})
544
+
545
+ _Ranked lower \u2014 fetch with \`get_specs\` / \`get_docs\` if you need them._
546
+
547
+ ` + tailLines.join("\n")
548
+ );
549
+ }
550
+ const itemsListed = blocks.length - itemsWithBody + tailLines.length;
551
+ const itemsOmitted = ranked.length - selectedGuardrails.length - blocks.length - tailLines.length;
552
+ const body = sections.join("\n\n");
553
+ const tokensUsed = estimateTokens(body);
554
+ const stats = {
555
+ budget,
556
+ tokensUsed,
557
+ itemsWithBody,
558
+ bodyFailures,
559
+ itemsListed,
560
+ itemsOmitted: Math.max(0, itemsOmitted),
561
+ guardrails: selectedGuardrails.length
562
+ };
563
+ if (sections.length === 0) {
564
+ return { text: "No relevant context found for this task.", stats };
565
+ }
566
+ const footer = `
567
+
568
+ ---
569
+ _Context: ${itemsWithBody} full text, ${itemsListed} listed, ${stats.itemsOmitted} omitted \xB7 ~${tokensUsed} of ${budget} tokens` + (bodyFailures > 0 ? ` \xB7 ${bodyFailures} body fetch(es) failed` : "") + `_`;
570
+ return { text: body + footer, stats };
571
+ }
572
+ export {
573
+ buildContext,
574
+ estimateTokens
575
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tool names are a public API: they are pinned in `.mcp.json` files and agent
3
+ * permission allowlists that customers have already committed to their own
4
+ * repositories. A rename is therefore additive — the new name is registered
5
+ * alongside the old one, and the old one keeps working.
6
+ *
7
+ * Nothing is removed before this date, and then only once usage telemetry shows
8
+ * no calls against the legacy name.
9
+ */
10
+ export declare const LEGACY_TOOL_REMOVAL_EARLIEST = "2027-01-31";
11
+ export declare function legacyToolDescription(canonical: string, description: string): string;
@@ -0,0 +1,7 @@
1
+ export interface LanguageInfo {
2
+ language: string;
3
+ categories: string[];
4
+ }
5
+ export declare function detectLanguage(filePath: string): LanguageInfo | undefined;
6
+ export declare function detectCategories(filePaths: string[]): string[];
7
+ export declare function detectLanguages(filePaths: string[]): string[];
@@ -0,0 +1,35 @@
1
+ export interface ScoredItem {
2
+ type: "guardrail" | "spec" | "doc" | "stack";
3
+ id: string;
4
+ title: string;
5
+ content: string;
6
+ score: number;
7
+ tokenEstimate: number;
8
+ }
9
+ interface ScorableItem {
10
+ type: "guardrail" | "spec" | "doc" | "stack";
11
+ id: string;
12
+ title: string;
13
+ content: string;
14
+ language?: string;
15
+ category?: string;
16
+ severity?: string;
17
+ updatedAt?: string;
18
+ }
19
+ export declare function estimateTokens(text: string): number;
20
+ /**
21
+ * Clip text to a token allowance, backing off to the last paragraph or line
22
+ * break in the final quarter so the cut lands between thoughts rather than
23
+ * mid-sentence.
24
+ */
25
+ export declare function truncateToTokens(text: string, maxTokens: number): {
26
+ text: string;
27
+ truncated: boolean;
28
+ };
29
+ export declare function scoreItems(items: ScorableItem[], taskDescription: string, filePaths: string[]): ScoredItem[];
30
+ export declare function selectWithinBudget(items: ScoredItem[], maxTokens: number): {
31
+ selected: ScoredItem[];
32
+ tokensUsed: number;
33
+ totalItems: number;
34
+ };
35
+ export {};
@@ -0,0 +1,8 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "./lib/api-client.js";
3
+ /**
4
+ * Build a SystemDox MCP server with all tools registered against the given
5
+ * API client. Shared by the stdio entry point (index.ts) and the hosted
6
+ * Streamable HTTP entry point (http.ts).
7
+ */
8
+ export declare function createServer(apiClient: SystemDoxApiClient): McpServer;
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "../lib/api-client.js";
3
+ export declare function registerCreateGuardrail(server: McpServer, apiClient: SystemDoxApiClient): void;
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "../lib/api-client.js";
3
+ export declare function registerCreateProjectRule(server: McpServer, apiClient: SystemDoxApiClient): void;
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "../lib/api-client.js";
3
+ export declare function registerCreatePromptTemplate(server: McpServer, apiClient: SystemDoxApiClient): void;
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "../lib/api-client.js";
3
+ export declare function registerGetContext(server: McpServer, apiClient: SystemDoxApiClient): void;
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "../lib/api-client.js";
3
+ export declare function registerGetDocs(server: McpServer, apiClient: SystemDoxApiClient): void;
@@ -0,0 +1,15 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "../lib/api-client.js";
3
+ export interface Guardrail {
4
+ id: string;
5
+ title: string;
6
+ description: string;
7
+ category: string;
8
+ severity: string;
9
+ pattern: string;
10
+ fix: string;
11
+ fitnessTest: string;
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ }
15
+ export declare function registerGetGuardrails(server: McpServer, apiClient: SystemDoxApiClient): void;
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "../lib/api-client.js";
3
+ export declare function registerGetPlanningContext(server: McpServer, apiClient: SystemDoxApiClient): void;
@@ -0,0 +1,9 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SystemDoxApiClient } from "../lib/api-client.js";
3
+ export interface ProjectRule {
4
+ id: string;
5
+ text: string;
6
+ category: string;
7
+ createdAt: string;
8
+ }
9
+ export declare function registerGetProjectRules(server: McpServer, apiClient: SystemDoxApiClient): void;