@decispher/mcp-server 0.1.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/tools.js ADDED
@@ -0,0 +1,314 @@
1
+ import { z } from 'zod';
2
+ import { readFileForContext } from './lib/file-reader.js';
3
+ const CAPTURE_TYPE_VALUES = [
4
+ 'decision', 'convention', 'constraint', 'rationale',
5
+ 'ownership', 'history', 'plan',
6
+ ];
7
+ function buildResult(text, client, extraMeta) {
8
+ const notices = client.lastNotices;
9
+ const prefix = notices?.truncated
10
+ ? '⚠️ Result was truncated to fit the token budget. Refine the query or request a smaller scope.\n\n'
11
+ : '';
12
+ const meta = { ...extraMeta };
13
+ if (notices) {
14
+ meta['decispher'] = {
15
+ truncated: notices.truncated,
16
+ citationCount: notices.citationCount,
17
+ };
18
+ }
19
+ return {
20
+ content: [{ type: 'text', text: `${prefix}${text}` }],
21
+ ...(Object.keys(meta).length > 0 ? { _meta: meta } : {}),
22
+ };
23
+ }
24
+ const askToolHandler = async (args, client) => {
25
+ const { question } = args;
26
+ const result = await client.ask(question);
27
+ const freshnessNote = (tier, ageInDays) => {
28
+ if (!tier || tier === 'fresh')
29
+ return '';
30
+ if (tier === 'fossil')
31
+ return ` ⚠️ fossil (${ageInDays}d — last reviewed over a year ago; verify before relying on this)`;
32
+ if (tier === 'stale')
33
+ return ` ⚠️ stale (${ageInDays}d)`;
34
+ if (tier === 'aging')
35
+ return ` ⏳ aging (${ageInDays}d)`;
36
+ return '';
37
+ };
38
+ const sourceList = result.sources
39
+ .map((s) => `- ${s.title} (similarity: ${(s.similarity * 100).toFixed(0)}%)${freshnessNote(s.freshnessTier, s.ageInDays)}`)
40
+ .join('\n');
41
+ return buildResult(`${result.answer}${sourceList ? `\n\nSources:\n${sourceList}` : ''}`, client, {
42
+ contextUnits: result.contextUnits ?? result.sources.map((s) => ({
43
+ id: s.id,
44
+ title: s.title,
45
+ lastReviewedAt: s.lastReviewedAt,
46
+ ageInDays: s.ageInDays,
47
+ freshnessTier: s.freshnessTier,
48
+ })),
49
+ });
50
+ };
51
+ const checkIntentHandler = async (args, client) => {
52
+ const { description, files } = args;
53
+ const result = await client.checkIntent(description, files);
54
+ const lines = [`verdict: ${result.verdict}`];
55
+ if (result.conflicts.length > 0) {
56
+ lines.push('\nconflicts:', ...result.conflicts.map((c) => ` [${c.severity}] ${c.title}\n ${c.explanation}`));
57
+ }
58
+ if (result.relevant.length > 0) {
59
+ lines.push('\nrelevant context:', ...result.relevant.map((r) => ` - [${r.type}] ${r.title}`));
60
+ }
61
+ return buildResult(lines.join('\n'), client);
62
+ };
63
+ /**
64
+ * Tool definitions — input schemas + handlers live here because the wire
65
+ * protocol with the MCP runtime is typed via Zod and the call body shapes
66
+ * are code-defined. Descriptions can be overridden at registration time by
67
+ * the server's /capabilities response (WS-E) so docs and gating live in
68
+ * one place server-side, but the executable bits stay client-side.
69
+ */
70
+ export const TOOL_DEFINITIONS = [
71
+ {
72
+ name: 'search_decisions',
73
+ defaultDescription: 'Semantic search across the team\'s context units (decisions, conventions, constraints, rationale, ownership, history, plans). Returns the units most similar to the query.',
74
+ inputSchema: {
75
+ query: z.string().describe('Search query — can be a question, topic, file path, or code snippet'),
76
+ limit: z.number().int().min(1).max(10).optional().describe('Maximum number of results to return (1–10, default 5)'),
77
+ types: z.array(z.enum(CAPTURE_TYPE_VALUES))
78
+ .min(1).max(7).optional()
79
+ .describe('Filter to one or more of the 7 context unit types: decision, convention, constraint, rationale, ownership, history, plan. Omit to search all types.'),
80
+ },
81
+ handler: async (args, client) => {
82
+ const { query, limit, types } = args;
83
+ const matches = await client.searchDecisions(query, { limit, types });
84
+ return buildResult(JSON.stringify(matches, null, 2), client);
85
+ },
86
+ },
87
+ {
88
+ name: 'get_constraints',
89
+ defaultDescription: 'Get all active architectural constraints — rules the team has decided must not be violated (e.g. "Do not use Express.js", "All DB access via Repository pattern").',
90
+ inputSchema: {
91
+ maxTokens: z.number().int().min(100).max(32000).optional().describe('Token budget for the response body. When the full list exceeds this, items are summarised (body dropped) and a cursor is returned.'),
92
+ cursor: z.string().optional().describe('Opaque pagination cursor returned by a previous truncated call. Resumes from the next page.'),
93
+ expand: z.array(z.string()).optional().describe('List of constraint IDs whose full body should always be included even when summarising the rest.'),
94
+ },
95
+ handler: async (args, client) => {
96
+ const { maxTokens, cursor, expand } = args;
97
+ const constraints = await client.getConstraints({ maxTokens, cursor, expand });
98
+ return buildResult(JSON.stringify(constraints, null, 2), client);
99
+ },
100
+ },
101
+ {
102
+ name: 'check_conventions',
103
+ defaultDescription: 'Get all active coding conventions for this codebase. Use before writing new code to ensure you follow established patterns.',
104
+ inputSchema: {
105
+ maxTokens: z.number().int().min(100).max(32000).optional().describe('Token budget for the response body. When the full list exceeds this, items are summarised (body dropped) and a cursor is returned.'),
106
+ cursor: z.string().optional().describe('Opaque pagination cursor returned by a previous truncated call. Resumes from the next page.'),
107
+ expand: z.array(z.string()).optional().describe('List of convention IDs whose full body should always be included even when summarising the rest.'),
108
+ },
109
+ handler: async (args, client) => {
110
+ const { maxTokens, cursor, expand } = args;
111
+ const conventions = await client.getConventions({ maxTokens, cursor, expand });
112
+ return buildResult(JSON.stringify(conventions, null, 2), client);
113
+ },
114
+ },
115
+ {
116
+ name: 'ask_knowledge_base',
117
+ defaultDescription: 'Ask a natural language question about the codebase, architecture, or past decisions. Returns an AI-synthesized answer with cited sources from the knowledge base.',
118
+ inputSchema: {
119
+ question: z.string().describe('Your question about the codebase, architecture, or past decisions'),
120
+ },
121
+ handler: askToolHandler,
122
+ },
123
+ {
124
+ name: 'get_context_for_file',
125
+ defaultDescription: 'Get relevant context units for a file (or up to 10 files in one call). Use before editing — pass a single `filePath` for one file, or `filePaths` (array) when refactoring a feature that spans multiple files. The MCP client reads each file from disk (256 KB cap per file; over-cap files are sliced head + tail) and the server fuses results into one ranked list (max similarity per match across files).',
126
+ inputSchema: {
127
+ filePath: z.string().optional().describe('Relative file path from the repo root (e.g. "src/services/auth.ts"). Use this for a single file; otherwise use filePaths.'),
128
+ filePaths: z.array(z.string()).min(1).max(10).optional().describe('Array of 1–10 relative file paths. Use this when a task spans multiple files; the server returns one fused, deduplicated, ranked match list — saves the round-trips of calling the tool N times.'),
129
+ limit: z.number().int().min(1).max(10).optional().describe('Maximum number of context matches to return (1–10, default 5)'),
130
+ },
131
+ handler: async (args, client) => {
132
+ const { filePath, filePaths, limit } = args;
133
+ const paths = filePaths && filePaths.length > 0
134
+ ? filePaths
135
+ : filePath
136
+ ? [filePath]
137
+ : null;
138
+ if (!paths) {
139
+ throw new Error('get_context_for_file: provide either `filePath` (single) or `filePaths` (1–10).');
140
+ }
141
+ const reads = await Promise.all(paths.map(async (p) => ({ path: p, read: await readFileForContext(p) })));
142
+ const files = reads.map(({ path, read }) => ({
143
+ filePath: path,
144
+ ...(read ? { fileContent: read.content, ...(read.language ? { language: read.language } : {}) } : {}),
145
+ }));
146
+ // Build the single-file options object only if at least one field is
147
+ // present — otherwise pass `undefined` so the wire contract matches
148
+ // the legacy "path-only" call shape (the client treats undefined +
149
+ // {} identically, but tests + downstream observers can tell them
150
+ // apart).
151
+ const singleOpts = paths.length === 1
152
+ ? (() => {
153
+ const opts = {};
154
+ if (files[0].fileContent !== undefined)
155
+ opts.fileContent = files[0].fileContent;
156
+ if (files[0].language !== undefined)
157
+ opts.language = files[0].language;
158
+ if (limit !== undefined)
159
+ opts.limit = limit;
160
+ return Object.keys(opts).length > 0 ? opts : undefined;
161
+ })()
162
+ : undefined;
163
+ const matches = paths.length === 1
164
+ ? await client.getContextForFile(paths[0], singleOpts)
165
+ : await client.getContextForFiles(files, limit !== undefined ? { limit } : undefined);
166
+ // Preserve the legacy single-file `_meta.fileRead` shape when called
167
+ // with a single path so existing consumers don't break. Batch calls
168
+ // get the new `_meta.fileReads` array.
169
+ if (paths.length === 1) {
170
+ const r = reads[0].read;
171
+ return buildResult(JSON.stringify(matches, null, 2), client, {
172
+ fileRead: r
173
+ ? { truncated: r.truncated, bytesRead: r.bytesRead, language: r.language }
174
+ : { truncated: false, bytesRead: 0, language: null, fallback: 'path-only' },
175
+ });
176
+ }
177
+ const fileReads = reads.map(({ path, read }) => ({
178
+ filePath: path,
179
+ ...(read
180
+ ? { truncated: read.truncated, bytesRead: read.bytesRead, language: read.language }
181
+ : { truncated: false, bytesRead: 0, language: null, fallback: 'path-only' }),
182
+ }));
183
+ return buildResult(JSON.stringify(matches, null, 2), client, { fileReads });
184
+ },
185
+ },
186
+ {
187
+ name: 'list_topics',
188
+ defaultDescription: 'List all topics available in this project — stable canonical slugs from the team\'s taxonomy. Free; the discovery primitive of the list_topics → get_context_for_topic → get_decision chain. Use this when overview.md predates a newly-added topic or when you need to confirm a topic id before calling get_context_for_topic.',
189
+ inputSchema: {},
190
+ handler: async (_args, client) => {
191
+ const topics = await client.listTopics();
192
+ return buildResult(JSON.stringify(topics, null, 2), client);
193
+ },
194
+ },
195
+ {
196
+ name: 'get_context_for_topic',
197
+ defaultDescription: 'Fetch the curated context cluster for a topic. Returns spine units inline (always-load CRITICAL rules) plus the IDs and titles of expansion units — call get_decision with any of those IDs for the full body. Use this when you are about to write code that touches a topic; it gives you everything the team has decided about that area without per-unit fetch churn. Pass `includeBodies: true` to inline expansion bodies in one shot (larger payload, higher cost).',
198
+ inputSchema: {
199
+ topic: z.string().min(1).max(128).describe('Topic ID — get from list_topics or from .decispher/overview.md'),
200
+ includeBodies: z.boolean().optional().describe('When true, includes full expansion bodies inline (more expensive). Default false.'),
201
+ maxTokens: z.number().int().min(500).max(32000).optional().describe('Token budget for the response. Default 8000.'),
202
+ },
203
+ handler: async (args, client) => {
204
+ const { topic, includeBodies, maxTokens } = args;
205
+ const result = await client.getContextForTopic(topic, {
206
+ ...(includeBodies !== undefined ? { includeBodies } : {}),
207
+ ...(maxTokens !== undefined ? { maxTokens } : {}),
208
+ });
209
+ return buildResult(JSON.stringify(result, null, 2), client);
210
+ },
211
+ },
212
+ {
213
+ name: 'get_decision',
214
+ defaultDescription: 'Fetch the full body of a specific context unit by ID. Every other MCP tool returns '
215
+ + 'IDs (search_decisions, get_constraints, check_conventions, ask_knowledge_base, '
216
+ + 'get_context_for_file, check_intent conflicts/relevant). Use this when you need '
217
+ + 'the complete rationale, alternatives, and affected files for any of those IDs.',
218
+ inputSchema: {
219
+ decisionId: z.string().min(1).describe('The ID of the context unit to fetch (from conflicts[].decisionId or search results)'),
220
+ },
221
+ handler: async (args, client) => {
222
+ const { decisionId } = args;
223
+ const detail = await client.getDecision(decisionId);
224
+ const lines = [
225
+ `[${detail.type}] ${detail.title}`,
226
+ `severity: ${detail.severity} status: ${detail.status}`,
227
+ ];
228
+ if (detail.problem)
229
+ lines.push(`\nproblem:\n${detail.problem}`);
230
+ lines.push(`\ndecision:\n${detail.decision}`);
231
+ if (detail.rationale)
232
+ lines.push(`\nrationale:\n${detail.rationale}`);
233
+ if (detail.alternatives?.length) {
234
+ lines.push('\nalternatives considered:');
235
+ for (const alt of detail.alternatives) {
236
+ lines.push(` - ${alt.option} (rejected: ${alt.reasonRejected})`);
237
+ }
238
+ }
239
+ if (detail.affectedFiles.length)
240
+ lines.push(`\naffected files: ${detail.affectedFiles.join(', ')}`);
241
+ if (detail.tags.length)
242
+ lines.push(`tags: ${detail.tags.join(', ')}`);
243
+ if (detail.supersedes)
244
+ lines.push(`supersedes: ${detail.supersedes}`);
245
+ if (detail.supersededBy)
246
+ lines.push(`superseded by: ${detail.supersededBy}`);
247
+ return buildResult(lines.join('\n'), client);
248
+ },
249
+ },
250
+ {
251
+ name: 'check_intent',
252
+ defaultDescription: 'Check if a planned action conflicts with team constraints before proceeding. '
253
+ + 'Call this BEFORE generating code or making architectural changes. '
254
+ + 'Returns BLOCKED (hard conflict — stop), WARN (tension — proceed with caution), or CLEAR (no known conflicts).',
255
+ inputSchema: {
256
+ description: z.string().describe('What you are about to do — be specific (e.g. "Replace Redis cache with in-memory Map in src/cache/client.ts")'),
257
+ files: z.array(z.string()).optional().describe('Files you will touch — helps scope the conflict search'),
258
+ },
259
+ handler: checkIntentHandler,
260
+ },
261
+ {
262
+ name: 'capture_decision',
263
+ defaultDescription: 'Write a new context unit back into the team\'s knowledge base. Use ONLY for '
264
+ + 'durable, reusable knowledge worth recalling next session — never to dump scratch '
265
+ + 'notes or restate what is already in the repository.\n\n'
266
+ + 'Choose ONE `type`:\n'
267
+ + ' • decision — "We chose X over Y because Z" (an architectural pick + rationale)\n'
268
+ + ' • convention — "We always do X this way" (a coding/process pattern the team follows)\n'
269
+ + ' • constraint — "We can\'t do X because Y" (a hard rule that must not be violated)\n'
270
+ + ' • rationale — "X works this way because Y" (the reason behind existing behaviour)\n'
271
+ + ' • ownership — "Person/team X owns Y" (who is responsible for what)\n'
272
+ + ' • history — "We tried X, it failed because Y" (a past attempt and its outcome)\n'
273
+ + ' • plan — "We\'re going to do X in the future" (a committed roadmap item)\n\n'
274
+ + 'If a near-duplicate already exists (cosine ≥ 0.95), the existing unit\'s id is '
275
+ + 'returned with `alreadyExists: true` and nothing is inserted. Captures are tagged '
276
+ + '`sourceType=agent_capture` and shown distinctly to humans in the dashboard.',
277
+ inputSchema: {
278
+ type: z.enum(CAPTURE_TYPE_VALUES)
279
+ .describe('Which of the 7 context unit types this capture is. Pick the most precise fit.'),
280
+ title: z.string().min(1).max(200).describe('Short, declarative summary (e.g. "Use Drizzle ORM, not Prisma")'),
281
+ decision: z.string().min(1).max(4000).describe('The substance of the knowledge — what was decided / observed / committed'),
282
+ rationale: z.string().min(1).max(4000).describe('WHY this is the case — the reasoning, evidence, or constraint that drove it'),
283
+ problem: z.string().max(4000).optional().describe('Optional — the problem this addresses, if framing as a decision'),
284
+ severity: z.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']).optional().describe('Impact if violated. Defaults to MEDIUM.'),
285
+ affectedFiles: z.array(z.string()).max(50).optional().describe('File paths this knowledge applies to (helps future retrieval target the right files)'),
286
+ tags: z.array(z.string()).max(20).optional().describe('Free-form tags for search/grouping (e.g. ["auth", "session"])'),
287
+ alternatives: z.array(z.object({
288
+ option: z.string().min(1).max(200),
289
+ reasonRejected: z.string().min(1).max(500),
290
+ })).max(10).optional().describe('Alternatives considered and why they were rejected — preserves the trade-off history'),
291
+ discoveryCostTokens: z.number().min(0).max(100000).optional().describe('How many tokens you spent to discover this. Capped at 100k; defaults to a type-based estimate.'),
292
+ },
293
+ handler: async (args, client) => {
294
+ const result = await client.captureDecision(args);
295
+ const line = result.alreadyExists
296
+ ? `already-known unit: ${result.id} (cosine ${(result.similarityToExisting ?? 0).toFixed(3)} ≥ 0.95). No new row created.`
297
+ : `captured: ${result.id} (status=active, fusionPending=${result.fusionPending})`;
298
+ return buildResult(line, client);
299
+ },
300
+ },
301
+ ];
302
+ export function registerTools(server, client, options = {}) {
303
+ const { enabledTools, descriptions } = options;
304
+ for (const def of TOOL_DEFINITIONS) {
305
+ if (enabledTools && !enabledTools.has(def.name))
306
+ continue;
307
+ const description = descriptions?.[def.name] ?? def.defaultDescription;
308
+ server.registerTool(def.name, {
309
+ description,
310
+ inputSchema: z.object(def.inputSchema),
311
+ }, async (args) => def.handler(args, client));
312
+ }
313
+ }
314
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@decispher/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "Decispher MCP server — serves engineering decisions to AI coding agents",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "decispher-mcp": "./dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "start": "node dist/index.js",
15
+ "test": "vitest run",
16
+ "typecheck": "tsc --noEmit",
17
+ "clean": "rimraf dist"
18
+ },
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.29.0",
21
+ "zod": "^3.22.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "rimraf": "^6.0.0",
26
+ "typescript": "^5.7.0",
27
+ "vitest": "^3.0.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "keywords": [
33
+ "mcp",
34
+ "decispher",
35
+ "decisions",
36
+ "context",
37
+ "engineering",
38
+ "knowledge"
39
+ ],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/gr8-alizaidi/decispher-platform"
43
+ }
44
+ }
@@ -0,0 +1,136 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { promises as fs } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
6
+ import {
7
+ readFileForContext,
8
+ detectLanguage,
9
+ DEFAULT_FILE_READ_MAX_BYTES,
10
+ } from '../lib/file-reader.js';
11
+
12
+ let workDir: string;
13
+
14
+ beforeEach(async () => {
15
+ workDir = join(tmpdir(), `decispher-file-reader-${randomUUID()}`);
16
+ await fs.mkdir(workDir, { recursive: true });
17
+ });
18
+
19
+ afterEach(async () => {
20
+ await fs.rm(workDir, { recursive: true, force: true });
21
+ });
22
+
23
+ describe('detectLanguage', () => {
24
+ it.each([
25
+ ['src/auth.ts', 'typescript'],
26
+ ['component.tsx', 'tsx'],
27
+ ['legacy.cjs', 'javascript'],
28
+ ['main.py', 'python'],
29
+ ['cmd.go', 'go'],
30
+ ['lib.rs', 'rust'],
31
+ ['App.java', 'java'],
32
+ ])('maps %s → %s', (path, lang) => {
33
+ expect(detectLanguage(path)).toBe(lang);
34
+ });
35
+
36
+ it('returns null for unknown extensions', () => {
37
+ expect(detectLanguage('README')).toBeNull();
38
+ expect(detectLanguage('config.yaml')).toBeNull();
39
+ });
40
+ });
41
+
42
+ describe('readFileForContext', () => {
43
+ it('reads small UTF-8 text files in full', async () => {
44
+ const path = join(workDir, 'a.ts');
45
+ await fs.writeFile(path, 'export const x = 1;\n', 'utf8');
46
+
47
+ const result = await readFileForContext(path);
48
+
49
+ expect(result).not.toBeNull();
50
+ expect(result!.content).toBe('export const x = 1;\n');
51
+ expect(result!.language).toBe('typescript');
52
+ expect(result!.truncated).toBe(false);
53
+ expect(result!.bytesRead).toBe(20);
54
+ });
55
+
56
+ it('strips a UTF-8 BOM if present', async () => {
57
+ const path = join(workDir, 'bom.ts');
58
+ await fs.writeFile(path, 'export const x = 1;', 'utf8');
59
+
60
+ const result = await readFileForContext(path);
61
+ expect(result!.content.startsWith('')).toBe(false);
62
+ expect(result!.content).toBe('export const x = 1;');
63
+ });
64
+
65
+ it('returns null when the path does not exist', async () => {
66
+ const result = await readFileForContext(join(workDir, 'missing.ts'));
67
+ expect(result).toBeNull();
68
+ });
69
+
70
+ it('returns null when the path points to a directory', async () => {
71
+ const result = await readFileForContext(workDir);
72
+ expect(result).toBeNull();
73
+ });
74
+
75
+ it('refuses files that look binary (NUL byte in head)', async () => {
76
+ const path = join(workDir, 'image.bin');
77
+ const buf = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x00, 0x01, 0x02, 0x03]);
78
+ await fs.writeFile(path, buf);
79
+
80
+ const result = await readFileForContext(path);
81
+ expect(result).toBeNull();
82
+ });
83
+
84
+ it('truncates oversize files head + tail and flags truncated=true', async () => {
85
+ const path = join(workDir, 'big.ts');
86
+ const head = 'HEAD_MARKER\n' + 'a'.repeat(10_000) + '\n';
87
+ const tail = 'b'.repeat(10_000) + '\nTAIL_MARKER\n';
88
+ await fs.writeFile(path, head + 'x'.repeat(2_000) + tail);
89
+
90
+ const result = await readFileForContext(path, { maxBytes: 1024 });
91
+ expect(result).not.toBeNull();
92
+ expect(result!.truncated).toBe(true);
93
+ expect(result!.bytesRead).toBe(1024);
94
+ expect(result!.content.startsWith('HEAD_MARKER')).toBe(true);
95
+ expect(result!.content.endsWith('TAIL_MARKER\n')).toBe(true);
96
+ expect(result!.content).toContain('truncated by MCP file-reader');
97
+ });
98
+
99
+ it('honors the default 256 KB cap when no override is provided', async () => {
100
+ const path = join(workDir, 'huge.ts');
101
+ // Just over the cap so we know it triggers truncation.
102
+ await fs.writeFile(path, 'x'.repeat(DEFAULT_FILE_READ_MAX_BYTES + 1024));
103
+
104
+ const result = await readFileForContext(path);
105
+ expect(result!.truncated).toBe(true);
106
+ expect(result!.bytesRead).toBe(DEFAULT_FILE_READ_MAX_BYTES);
107
+ });
108
+
109
+ it('emitted content (head + marker + tail) never exceeds maxBytes', async () => {
110
+ // Guards against the marker pushing payload past the server-side
111
+ // fileContent maxLength (262144 bytes). Regression test for the
112
+ // off-by-marker-length bug.
113
+ const path = join(workDir, 'cap.ts');
114
+ await fs.writeFile(path, 'x'.repeat(DEFAULT_FILE_READ_MAX_BYTES * 2));
115
+
116
+ const result = await readFileForContext(path);
117
+ expect(result!.truncated).toBe(true);
118
+ expect(Buffer.byteLength(result!.content, 'utf8')).toBeLessThanOrEqual(DEFAULT_FILE_READ_MAX_BYTES);
119
+ });
120
+
121
+ it('resolves relative paths against the supplied cwd', async () => {
122
+ await fs.writeFile(join(workDir, 'rel.ts'), 'export const y = 2;');
123
+
124
+ const result = await readFileForContext('rel.ts', { cwd: workDir });
125
+ expect(result!.content).toBe('export const y = 2;');
126
+ });
127
+
128
+ it('returns language=null for unknown extensions but still reads content', async () => {
129
+ const path = join(workDir, 'notes.md');
130
+ await fs.writeFile(path, '# heading\nbody');
131
+
132
+ const result = await readFileForContext(path);
133
+ expect(result!.language).toBeNull();
134
+ expect(result!.content).toBe('# heading\nbody');
135
+ });
136
+ });