syntax-map-mcp 0.1.8 → 1.0.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 CHANGED
@@ -1,9 +1,11 @@
1
1
  import { z } from 'zod';
2
+ import { getAstTree as getAstTreeAnalysis } from './analysis/ast-tree.js';
2
3
  import { buildContext as buildContextAnalysis } from './analysis/context.js';
3
4
  import { findDefinitions } from './analysis/definitions.js';
4
5
  import { clearIndex as clearWorkspaceIndex, findIndexedDefinitions, findIndexedReferences, getIndexStatus as getWorkspaceIndexStatus, indexWorkspace as indexWorkspaceAnalysis, searchSymbols as searchIndexedSymbols } from './analysis/index.js';
5
6
  import { runTreeSitterQuery } from './analysis/query.js';
6
7
  import { findReferences as findReferencesAnalysis } from './analysis/references.js';
8
+ import { getCompletion as getLspCompletion, getDefinition as getLspDefinition, getDocumentSymbols as getLspDocumentSymbols, getHover as getLspHover, getReferences as getLspReferences, getSignatureHelp as getLspSignatureHelp, getWorkspaceSymbols as getLspWorkspaceSymbols } from './analysis/lsp.js';
7
9
  import { summarizeFile as summarizeFileAnalysis } from './analysis/summary.js';
8
10
  import { listSymbols as listParsedSymbols } from './analysis/symbols.js';
9
11
  import { parseSourceFile } from './parser.js';
@@ -11,6 +13,8 @@ import { jsonResult, toolFailure } from './result.js';
11
13
  const symbolKindSchema = z.enum(['function', 'method', 'class', 'variable', 'interface', 'type']);
12
14
  const detailSchema = z.enum(['compact', 'full']);
13
15
  const contextLineCountSchema = z.number().int().min(0).max(10);
16
+ const astDepthSchema = z.number().int().min(0).max(20);
17
+ const lspPositionSchema = z.number().int().min(0);
14
18
  export function createToolHandlers(workspace) {
15
19
  return {
16
20
  async listSymbols(input) {
@@ -18,6 +22,7 @@ export function createToolHandlers(workspace) {
18
22
  if (!file.ok)
19
23
  return toolFailure(file.error.code, file.error.message);
20
24
  const parsed = parseSourceFile(file);
25
+ /* v8 ignore next -- parser failures are covered in parser and summarize handler tests. */
21
26
  if (!parsed.ok)
22
27
  return toolFailure(parsed.error.code, parsed.error.message);
23
28
  return jsonResult({
@@ -50,6 +55,7 @@ export function createToolHandlers(workspace) {
50
55
  if (!file.ok)
51
56
  return toolFailure(file.error.code, file.error.message);
52
57
  const parsed = parseSourceFile(file);
58
+ /* v8 ignore next -- parser failure handling is covered by parser tests. */
53
59
  if (!parsed.ok)
54
60
  return toolFailure(parsed.error.code, parsed.error.message);
55
61
  const result = runTreeSitterQuery(parsed, input.query);
@@ -57,6 +63,54 @@ export function createToolHandlers(workspace) {
57
63
  return toolFailure(result.error.code, result.error.message);
58
64
  return jsonResult(result);
59
65
  },
66
+ async getAstTree(input) {
67
+ const result = await getAstTreeAnalysis(workspace, input);
68
+ if (!result.ok)
69
+ return toolFailure(result.error.code, result.error.message);
70
+ return jsonResult(result);
71
+ },
72
+ async lspDocumentSymbols(input) {
73
+ const result = await getLspDocumentSymbols(workspace, input);
74
+ if (!result.ok)
75
+ return toolFailure(result.error.code, result.error.message);
76
+ return jsonResult(result);
77
+ },
78
+ async lspDefinition(input) {
79
+ const result = await getLspDefinition(workspace, input);
80
+ if (!result.ok)
81
+ return toolFailure(result.error.code, result.error.message);
82
+ return jsonResult(result);
83
+ },
84
+ async lspReferences(input) {
85
+ const result = await getLspReferences(workspace, input);
86
+ if (!result.ok)
87
+ return toolFailure(result.error.code, result.error.message);
88
+ return jsonResult(result);
89
+ },
90
+ async lspHover(input) {
91
+ const result = await getLspHover(workspace, input);
92
+ if (!result.ok)
93
+ return toolFailure(result.error.code, result.error.message);
94
+ return jsonResult(result);
95
+ },
96
+ async lspWorkspaceSymbols(input) {
97
+ const result = await getLspWorkspaceSymbols(workspace, input);
98
+ if (!result.ok)
99
+ return toolFailure(result.error.code, result.error.message);
100
+ return jsonResult(result);
101
+ },
102
+ async lspCompletion(input) {
103
+ const result = await getLspCompletion(workspace, input);
104
+ if (!result.ok)
105
+ return toolFailure(result.error.code, result.error.message);
106
+ return jsonResult(result);
107
+ },
108
+ async lspSignatureHelp(input) {
109
+ const result = await getLspSignatureHelp(workspace, input);
110
+ if (!result.ok)
111
+ return toolFailure(result.error.code, result.error.message);
112
+ return jsonResult(result);
113
+ },
60
114
  async buildContext(input) {
61
115
  const result = await buildContextAnalysis(workspace, input);
62
116
  if (!result.ok)
@@ -89,12 +143,14 @@ export function createToolHandlers(workspace) {
89
143
  },
90
144
  async getIndexStatus(_input) {
91
145
  const result = await getWorkspaceIndexStatus(workspace);
146
+ /* v8 ignore next -- getIndexStatus read failures are defensive and covered at index layer. */
92
147
  if (!result.ok)
93
148
  return toolFailure(result.error.code, result.error.message);
94
149
  return jsonResult(result);
95
150
  },
96
151
  async clearIndex(_input) {
97
152
  const result = await clearWorkspaceIndex(workspace);
153
+ /* v8 ignore next -- clearIndex failure is covered at index layer. */
98
154
  if (!result.ok)
99
155
  return toolFailure(result.error.code, result.error.message);
100
156
  return jsonResult(result);
@@ -142,6 +198,83 @@ export function registerTools(server, workspace) {
142
198
  query: z.string()
143
199
  }
144
200
  }, handlers.runQuery);
201
+ server.registerTool('get_ast_tree', {
202
+ title: 'Get AST tree',
203
+ description: 'Return a depth-limited tree-sitter AST for one supported source file.',
204
+ inputSchema: {
205
+ path: z.string(),
206
+ maxDepth: astDepthSchema.optional(),
207
+ includeText: z.boolean().optional()
208
+ }
209
+ }, handlers.getAstTree);
210
+ server.registerTool('lsp_document_symbols', {
211
+ title: 'LSP document symbols',
212
+ description: 'Return tree-sitter symbols converted to LSP DocumentSymbol ranges and kinds.',
213
+ inputSchema: {
214
+ path: z.string()
215
+ }
216
+ }, handlers.lspDocumentSymbols);
217
+ server.registerTool('lsp_definition', {
218
+ title: 'LSP definition',
219
+ description: 'Return definition locations for the identifier at a zero-based LSP position.',
220
+ inputSchema: {
221
+ path: z.string(),
222
+ line: lspPositionSchema,
223
+ character: lspPositionSchema,
224
+ paths: z.array(z.string()).optional()
225
+ }
226
+ }, handlers.lspDefinition);
227
+ server.registerTool('lsp_references', {
228
+ title: 'LSP references',
229
+ description: 'Return reference locations for the identifier at a zero-based LSP position.',
230
+ inputSchema: {
231
+ path: z.string(),
232
+ line: lspPositionSchema,
233
+ character: lspPositionSchema,
234
+ paths: z.array(z.string()).optional()
235
+ }
236
+ }, handlers.lspReferences);
237
+ server.registerTool('lsp_hover', {
238
+ title: 'LSP hover',
239
+ description: 'Return markdown hover contents for the identifier at a zero-based LSP position.',
240
+ inputSchema: {
241
+ path: z.string(),
242
+ line: lspPositionSchema,
243
+ character: lspPositionSchema,
244
+ paths: z.array(z.string()).optional()
245
+ }
246
+ }, handlers.lspHover);
247
+ server.registerTool('lsp_workspace_symbols', {
248
+ title: 'LSP workspace symbols',
249
+ description: 'Return workspace symbols matching a case-insensitive query.',
250
+ inputSchema: {
251
+ query: z.string(),
252
+ paths: z.array(z.string()).optional(),
253
+ kinds: z.array(symbolKindSchema).optional()
254
+ }
255
+ }, handlers.lspWorkspaceSymbols);
256
+ server.registerTool('lsp_completion', {
257
+ title: 'LSP completion',
258
+ description: 'Return workspace symbol completion items for the prefix before a zero-based LSP position.',
259
+ inputSchema: {
260
+ path: z.string(),
261
+ line: lspPositionSchema,
262
+ character: lspPositionSchema,
263
+ paths: z.array(z.string()).optional(),
264
+ kinds: z.array(symbolKindSchema).optional(),
265
+ limit: z.number().int().positive().max(500).optional()
266
+ }
267
+ }, handlers.lspCompletion);
268
+ server.registerTool('lsp_signature_help', {
269
+ title: 'LSP signature help',
270
+ description: 'Return signature help for the active call at a zero-based LSP position.',
271
+ inputSchema: {
272
+ path: z.string(),
273
+ line: lspPositionSchema,
274
+ character: lspPositionSchema,
275
+ paths: z.array(z.string()).optional()
276
+ }
277
+ }, handlers.lspSignatureHelp);
145
278
  server.registerTool('build_context', {
146
279
  title: 'Build context',
147
280
  description: 'Build markdown context for supported source files.',
package/dist/workspace.js CHANGED
@@ -27,6 +27,7 @@ function isGitignored(absolutePath, matchers) {
27
27
  let ignored = false;
28
28
  for (const { baseDirectory, matcher } of matchers) {
29
29
  const relativePath = path.relative(baseDirectory, absolutePath);
30
+ /* v8 ignore next 3 -- matcher base directories are loaded from workspace traversal roots. */
30
31
  if (relativePath === '' || relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
31
32
  continue;
32
33
  }
@@ -34,6 +35,7 @@ function isGitignored(absolutePath, matchers) {
34
35
  if (result.ignored) {
35
36
  ignored = true;
36
37
  }
38
+ /* v8 ignore next 3 -- unignore precedence is covered by end-to-end gitignore tests. */
37
39
  if (result.unignored) {
38
40
  ignored = false;
39
41
  }
@@ -99,14 +101,17 @@ export async function createWorkspace(workspaceRoot) {
99
101
  let actualPath;
100
102
  try {
101
103
  actualPath = await realpath(absolutePath);
104
+ /* v8 ignore next 3 -- file disappeared between readdir and realpath. */
102
105
  }
103
106
  catch {
104
107
  continue;
105
108
  }
109
+ /* v8 ignore next 4 -- explicit symlink escape checks are covered by readSourceFile tests. */
106
110
  if (!isInsideRoot(root, actualPath)) {
107
111
  continue;
108
112
  }
109
113
  const fileStat = await stat(actualPath);
114
+ /* v8 ignore next 4 -- entry was a file at readdir time; this guards filesystem races. */
110
115
  if (!fileStat.isFile()) {
111
116
  continue;
112
117
  }
package/docs/tools.md CHANGED
@@ -140,6 +140,272 @@ syntax-map-mcp의 주요 MCP 도구 입력과 응답 예시입니다. 응답 예
140
140
  }
141
141
  ```
142
142
 
143
+ ## get_ast_tree
144
+
145
+ 입력:
146
+
147
+ ```json
148
+ {
149
+ "path": "src/users.ts",
150
+ "maxDepth": 2,
151
+ "includeText": false
152
+ }
153
+ ```
154
+
155
+ 응답 일부:
156
+
157
+ ```json
158
+ {
159
+ "ok": true,
160
+ "path": "src/users.ts",
161
+ "language": "typescript",
162
+ "tree": {
163
+ "root": {
164
+ "type": "program",
165
+ "named": true,
166
+ "childCount": 3,
167
+ "range": {
168
+ "start": { "row": 0, "column": 0 },
169
+ "end": { "row": 12, "column": 0 }
170
+ },
171
+ "children": [
172
+ {
173
+ "type": "export_statement",
174
+ "named": true,
175
+ "childCount": 1,
176
+ "children": []
177
+ }
178
+ ]
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ ## lsp_document_symbols
185
+
186
+ 입력:
187
+
188
+ ```json
189
+ {
190
+ "path": "src/users.ts"
191
+ }
192
+ ```
193
+
194
+ 응답 일부:
195
+
196
+ ```json
197
+ {
198
+ "ok": true,
199
+ "path": "src/users.ts",
200
+ "language": "typescript",
201
+ "symbols": [
202
+ {
203
+ "name": "UserService",
204
+ "kind": 5,
205
+ "range": {
206
+ "start": { "line": 7, "character": 7 },
207
+ "end": { "line": 13, "character": 1 }
208
+ },
209
+ "selectionRange": {
210
+ "start": { "line": 7, "character": 13 },
211
+ "end": { "line": 7, "character": 24 }
212
+ }
213
+ }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ ## lsp_definition
219
+
220
+ 입력:
221
+
222
+ ```json
223
+ {
224
+ "path": "src/users.ts",
225
+ "line": 21,
226
+ "character": 2
227
+ }
228
+ ```
229
+
230
+ 응답 일부:
231
+
232
+ ```json
233
+ {
234
+ "ok": true,
235
+ "path": "src/users.ts",
236
+ "language": "typescript",
237
+ "name": "formatUser",
238
+ "locations": [
239
+ {
240
+ "path": "src/users.ts",
241
+ "range": {
242
+ "start": { "line": 15, "character": 7 },
243
+ "end": { "line": 17, "character": 1 }
244
+ }
245
+ }
246
+ ]
247
+ }
248
+ ```
249
+
250
+ ## lsp_references
251
+
252
+ 입력:
253
+
254
+ ```json
255
+ {
256
+ "path": "src/users.ts",
257
+ "line": 21,
258
+ "character": 2
259
+ }
260
+ ```
261
+
262
+ 응답 일부:
263
+
264
+ ```json
265
+ {
266
+ "ok": true,
267
+ "path": "src/users.ts",
268
+ "language": "typescript",
269
+ "name": "formatUser",
270
+ "locations": [
271
+ {
272
+ "path": "src/users.ts",
273
+ "range": {
274
+ "start": { "line": 21, "character": 0 },
275
+ "end": { "line": 21, "character": 10 }
276
+ }
277
+ }
278
+ ]
279
+ }
280
+ ```
281
+
282
+ ## lsp_hover
283
+
284
+ 입력:
285
+
286
+ ```json
287
+ {
288
+ "path": "src/users.ts",
289
+ "line": 21,
290
+ "character": 2
291
+ }
292
+ ```
293
+
294
+ 응답 일부:
295
+
296
+ ```json
297
+ {
298
+ "ok": true,
299
+ "path": "src/users.ts",
300
+ "language": "typescript",
301
+ "name": "formatUser",
302
+ "range": {
303
+ "start": { "line": 21, "character": 0 },
304
+ "end": { "line": 21, "character": 10 }
305
+ },
306
+ "contents": {
307
+ "kind": "markdown",
308
+ "value": "**function** `formatUser`\n\n```typescript\nexport function formatUser(user: User): string {\n```"
309
+ }
310
+ }
311
+ ```
312
+
313
+ ## lsp_workspace_symbols
314
+
315
+ 입력:
316
+
317
+ ```json
318
+ {
319
+ "query": "Service"
320
+ }
321
+ ```
322
+
323
+ 응답 일부:
324
+
325
+ ```json
326
+ {
327
+ "ok": true,
328
+ "query": "Service",
329
+ "symbols": [
330
+ {
331
+ "name": "UserService",
332
+ "kind": 5,
333
+ "location": {
334
+ "path": "src/users.ts",
335
+ "range": {
336
+ "start": { "line": 7, "character": 7 },
337
+ "end": { "line": 13, "character": 1 }
338
+ }
339
+ }
340
+ }
341
+ ]
342
+ }
343
+ ```
344
+
345
+ ## lsp_completion
346
+
347
+ 입력:
348
+
349
+ ```json
350
+ {
351
+ "path": "src/users.ts",
352
+ "line": 21,
353
+ "character": 3,
354
+ "limit": 20
355
+ }
356
+ ```
357
+
358
+ 응답 일부:
359
+
360
+ ```json
361
+ {
362
+ "ok": true,
363
+ "path": "src/users.ts",
364
+ "language": "typescript",
365
+ "prefix": "for",
366
+ "isIncomplete": false,
367
+ "items": [
368
+ {
369
+ "label": "formatUser",
370
+ "kind": 3,
371
+ "detail": "function from src/users.ts",
372
+ "sortText": "formatUser"
373
+ }
374
+ ]
375
+ }
376
+ ```
377
+
378
+ ## lsp_signature_help
379
+
380
+ 입력:
381
+
382
+ ```json
383
+ {
384
+ "path": "src/users.ts",
385
+ "line": 21,
386
+ "character": 11
387
+ }
388
+ ```
389
+
390
+ 응답 일부:
391
+
392
+ ```json
393
+ {
394
+ "ok": true,
395
+ "path": "src/users.ts",
396
+ "language": "typescript",
397
+ "name": "formatUser",
398
+ "activeSignature": 0,
399
+ "activeParameter": 0,
400
+ "signatures": [
401
+ {
402
+ "label": "formatUser(user: User): string",
403
+ "parameters": [{ "label": "user: User" }]
404
+ }
405
+ ]
406
+ }
407
+ ```
408
+
143
409
  ## build_context
144
410
 
145
411
  파일 경로 기반 입력:
@@ -215,6 +481,7 @@ syntax-map-mcp의 주요 MCP 도구 입력과 응답 예시입니다. 응답 예
215
481
  ```json
216
482
  {
217
483
  "ok": true,
484
+ "schemaVersion": 1,
218
485
  "indexedFiles": 12,
219
486
  "symbols": 84,
220
487
  "references": 231,
@@ -331,10 +598,21 @@ syntax-map-mcp의 주요 MCP 도구 입력과 응답 예시입니다. 응답 예
331
598
  ```json
332
599
  {
333
600
  "ok": true,
601
+ "schemaVersion": 1,
334
602
  "indexedFiles": 12,
335
603
  "symbols": 84,
336
604
  "references": 231,
337
- "staleFiles": 0
605
+ "staleFiles": 2,
606
+ "staleReasons": [
607
+ {
608
+ "path": "src/users.ts",
609
+ "reason": "changed"
610
+ },
611
+ {
612
+ "path": "src/old.ts",
613
+ "reason": "missing"
614
+ }
615
+ ]
338
616
  }
339
617
  ```
340
618
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "syntax-map-mcp",
3
- "version": "0.1.8",
3
+ "version": "1.0.0",
4
4
  "description": "Tree-sitter based code analysis MCP server",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,9 +29,10 @@
29
29
  "dev": "tsx src/cli.ts",
30
30
  "build": "tsc -p tsconfig.json",
31
31
  "test": "vitest run",
32
+ "coverage": "vitest run --coverage",
32
33
  "test:watch": "vitest",
33
34
  "typecheck": "tsc -p tsconfig.json --noEmit",
34
- "release:check": "npm run typecheck && npm test && npm run build && node scripts/check-package-files.mjs && node scripts/smoke-package-install.mjs"
35
+ "release:check": "npm run typecheck && npm run coverage && npm run build && node scripts/check-package-files.mjs && node scripts/smoke-package-install.mjs"
35
36
  },
36
37
  "dependencies": {
37
38
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -46,6 +47,7 @@
46
47
  },
47
48
  "devDependencies": {
48
49
  "@types/node": "^22.0.0",
50
+ "@vitest/coverage-v8": "^3.2.4",
49
51
  "tsx": "^4.0.0",
50
52
  "typescript": "^5.8.0",
51
53
  "vitest": "^3.0.0"