mdcontext 0.0.1 → 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/.changeset/README.md +28 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/ci.yml +83 -0
- package/.github/workflows/release.yml +113 -0
- package/.tldrignore +112 -0
- package/AGENTS.md +46 -0
- package/BACKLOG.md +338 -0
- package/README.md +231 -11
- package/biome.json +36 -0
- package/cspell.config.yaml +14 -0
- package/dist/chunk-KRYIFLQR.js +92 -0
- package/dist/chunk-S7E6TFX6.js +742 -0
- package/dist/chunk-VVTGZNBT.js +1519 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +2015 -0
- package/dist/index.d.ts +266 -0
- package/dist/index.js +86 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +376 -0
- package/docs/019-USAGE.md +586 -0
- package/docs/020-current-implementation.md +364 -0
- package/docs/021-DOGFOODING-FINDINGS.md +175 -0
- package/docs/BACKLOG.md +80 -0
- package/docs/DESIGN.md +439 -0
- package/docs/PROJECT.md +88 -0
- package/docs/ROADMAP.md +407 -0
- package/docs/test-links.md +9 -0
- package/package.json +69 -10
- package/pnpm-workspace.yaml +5 -0
- package/research/config-analysis/01-current-implementation.md +470 -0
- package/research/config-analysis/02-strategy-recommendation.md +428 -0
- package/research/config-analysis/03-task-candidates.md +715 -0
- package/research/config-analysis/033-research-configuration-management.md +828 -0
- package/research/config-analysis/034-research-effect-cli-config.md +1504 -0
- package/research/config-analysis/04-consolidated-task-candidates.md +277 -0
- package/research/dogfood/consolidated-tool-evaluation.md +373 -0
- package/research/dogfood/strategy-a/a-synthesis.md +184 -0
- package/research/dogfood/strategy-a/a1-docs.md +226 -0
- package/research/dogfood/strategy-a/a2-amorphic.md +156 -0
- package/research/dogfood/strategy-a/a3-llm.md +164 -0
- package/research/dogfood/strategy-b/b-synthesis.md +228 -0
- package/research/dogfood/strategy-b/b1-architecture.md +207 -0
- package/research/dogfood/strategy-b/b2-gaps.md +258 -0
- package/research/dogfood/strategy-b/b3-workflows.md +250 -0
- package/research/dogfood/strategy-c/c-synthesis.md +451 -0
- package/research/dogfood/strategy-c/c1-explorer.md +192 -0
- package/research/dogfood/strategy-c/c2-diver-memory.md +145 -0
- package/research/dogfood/strategy-c/c3-diver-control.md +148 -0
- package/research/dogfood/strategy-c/c4-diver-failure.md +151 -0
- package/research/dogfood/strategy-c/c5-diver-execution.md +221 -0
- package/research/dogfood/strategy-c/c6-diver-org.md +221 -0
- package/research/effect-cli-error-handling.md +845 -0
- package/research/effect-errors-as-values.md +943 -0
- package/research/errors-task-analysis/00-consolidated-tasks.md +207 -0
- package/research/errors-task-analysis/cli-commands-analysis.md +909 -0
- package/research/errors-task-analysis/embeddings-analysis.md +709 -0
- package/research/errors-task-analysis/index-search-analysis.md +812 -0
- package/research/mdcontext-error-analysis.md +521 -0
- package/research/npm_publish/011-npm-workflow-research-agent2.md +792 -0
- package/research/npm_publish/012-npm-workflow-research-agent1.md +530 -0
- package/research/npm_publish/013-npm-workflow-research-agent3.md +722 -0
- package/research/npm_publish/014-npm-workflow-synthesis.md +556 -0
- package/research/npm_publish/031-npm-workflow-task-analysis.md +134 -0
- package/research/semantic-search/002-research-embedding-models.md +490 -0
- package/research/semantic-search/003-research-rag-alternatives.md +523 -0
- package/research/semantic-search/004-research-vector-search.md +841 -0
- package/research/semantic-search/032-research-semantic-search.md +427 -0
- package/research/task-management-2026/00-synthesis-recommendations.md +295 -0
- package/research/task-management-2026/01-ai-workflow-tools.md +416 -0
- package/research/task-management-2026/02-agent-framework-patterns.md +476 -0
- package/research/task-management-2026/03-lightweight-file-based.md +567 -0
- package/research/task-management-2026/04-established-tools-ai-features.md +541 -0
- package/research/task-management-2026/linear/01-core-features-workflow.md +771 -0
- package/research/task-management-2026/linear/02-api-integrations.md +930 -0
- package/research/task-management-2026/linear/03-ai-features.md +368 -0
- package/research/task-management-2026/linear/04-pricing-setup.md +205 -0
- package/research/task-management-2026/linear/05-usage-patterns-best-practices.md +605 -0
- package/scripts/rebuild-hnswlib.js +63 -0
- package/src/cli/argv-preprocessor.test.ts +210 -0
- package/src/cli/argv-preprocessor.ts +202 -0
- package/src/cli/cli.test.ts +430 -0
- package/src/cli/commands/backlinks.ts +54 -0
- package/src/cli/commands/context.ts +197 -0
- package/src/cli/commands/index-cmd.ts +300 -0
- package/src/cli/commands/index.ts +13 -0
- package/src/cli/commands/links.ts +52 -0
- package/src/cli/commands/search.ts +451 -0
- package/src/cli/commands/stats.ts +146 -0
- package/src/cli/commands/tree.ts +107 -0
- package/src/cli/flag-schemas.ts +275 -0
- package/src/cli/help.ts +386 -0
- package/src/cli/index.ts +9 -0
- package/src/cli/main.ts +145 -0
- package/src/cli/options.ts +31 -0
- package/src/cli/typo-suggester.test.ts +105 -0
- package/src/cli/typo-suggester.ts +130 -0
- package/src/cli/utils.ts +126 -0
- package/src/core/index.ts +1 -0
- package/src/core/types.ts +140 -0
- package/src/embeddings/index.ts +8 -0
- package/src/embeddings/openai-provider.ts +165 -0
- package/src/embeddings/semantic-search.ts +583 -0
- package/src/embeddings/types.ts +82 -0
- package/src/embeddings/vector-store.ts +299 -0
- package/src/index/index.ts +4 -0
- package/src/index/indexer.ts +446 -0
- package/src/index/storage.ts +196 -0
- package/src/index/types.ts +109 -0
- package/src/index/watcher.ts +131 -0
- package/src/index.ts +8 -0
- package/src/mcp/server.ts +483 -0
- package/src/parser/index.ts +1 -0
- package/src/parser/parser.test.ts +291 -0
- package/src/parser/parser.ts +395 -0
- package/src/parser/section-filter.ts +270 -0
- package/src/search/query-parser.test.ts +260 -0
- package/src/search/query-parser.ts +319 -0
- package/src/search/searcher.test.ts +182 -0
- package/src/search/searcher.ts +602 -0
- package/src/summarize/budget-bugs.test.ts +620 -0
- package/src/summarize/formatters.ts +419 -0
- package/src/summarize/index.ts +20 -0
- package/src/summarize/summarizer.test.ts +275 -0
- package/src/summarize/summarizer.ts +528 -0
- package/src/summarize/verify-bugs.test.ts +238 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/tokens.test.ts +142 -0
- package/src/utils/tokens.ts +186 -0
- package/tests/fixtures/cli/.mdcontext/config.json +8 -0
- package/tests/fixtures/cli/.mdcontext/indexes/documents.json +33 -0
- package/tests/fixtures/cli/.mdcontext/indexes/links.json +12 -0
- package/tests/fixtures/cli/.mdcontext/indexes/sections.json +233 -0
- package/tests/fixtures/cli/.mdcontext/vectors.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/vectors.meta.json +1264 -0
- package/tests/fixtures/cli/README.md +9 -0
- package/tests/fixtures/cli/api-reference.md +11 -0
- package/tests/fixtures/cli/getting-started.md +11 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +21 -0
- package/vitest.setup.ts +12 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server for mdcontext
|
|
5
|
+
*
|
|
6
|
+
* Exposes markdown analysis tools for Claude integration
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as path from 'node:path'
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
12
|
+
import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js'
|
|
13
|
+
import {
|
|
14
|
+
CallToolRequestSchema,
|
|
15
|
+
ListToolsRequestSchema,
|
|
16
|
+
} from '@modelcontextprotocol/sdk/types.js'
|
|
17
|
+
import { Effect } from 'effect'
|
|
18
|
+
import type { MdSection } from '../core/types.js'
|
|
19
|
+
import { semanticSearch } from '../embeddings/semantic-search.js'
|
|
20
|
+
import { buildIndex } from '../index/indexer.js'
|
|
21
|
+
import { parseFile } from '../parser/parser.js'
|
|
22
|
+
import { search } from '../search/searcher.js'
|
|
23
|
+
import { formatSummary, summarizeFile } from '../summarize/summarizer.js'
|
|
24
|
+
|
|
25
|
+
// Type alias for tool results - uses the SDK type
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Tool Definitions
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
const tools: Tool[] = [
|
|
32
|
+
{
|
|
33
|
+
name: 'md_search',
|
|
34
|
+
description:
|
|
35
|
+
'Search markdown documents by meaning using semantic search. Returns relevant sections based on natural language queries.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
query: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Natural language search query',
|
|
42
|
+
},
|
|
43
|
+
limit: {
|
|
44
|
+
type: 'number',
|
|
45
|
+
description: 'Maximum number of results (default: 5)',
|
|
46
|
+
default: 5,
|
|
47
|
+
},
|
|
48
|
+
path_filter: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description:
|
|
51
|
+
"Glob pattern to filter files (e.g., '*.md', 'docs/**/*.md')",
|
|
52
|
+
},
|
|
53
|
+
threshold: {
|
|
54
|
+
type: 'number',
|
|
55
|
+
description: 'Minimum similarity threshold 0-1 (default: 0.5)',
|
|
56
|
+
default: 0.5,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ['query'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'md_context',
|
|
64
|
+
description:
|
|
65
|
+
'Get LLM-ready context from a markdown file. Provides compressed, token-efficient summaries at various detail levels.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
path: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Path to the markdown file',
|
|
72
|
+
},
|
|
73
|
+
level: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
enum: ['full', 'summary', 'brief'],
|
|
76
|
+
description: 'Compression level (default: summary)',
|
|
77
|
+
default: 'summary',
|
|
78
|
+
},
|
|
79
|
+
max_tokens: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Maximum tokens to include in output',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ['path'],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'md_structure',
|
|
89
|
+
description:
|
|
90
|
+
'Get the structure/outline of a markdown file. Shows heading hierarchy with token counts.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
path: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Path to the markdown file',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ['path'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'md_keyword_search',
|
|
104
|
+
description:
|
|
105
|
+
'Search markdown documents by keyword search (headings, code blocks, lists, tables).',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
heading: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
description: 'Filter by heading pattern (regex)',
|
|
112
|
+
},
|
|
113
|
+
path_filter: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
description: 'Glob pattern to filter files',
|
|
116
|
+
},
|
|
117
|
+
has_code: {
|
|
118
|
+
type: 'boolean',
|
|
119
|
+
description: 'Only sections with code blocks',
|
|
120
|
+
},
|
|
121
|
+
has_list: {
|
|
122
|
+
type: 'boolean',
|
|
123
|
+
description: 'Only sections with lists',
|
|
124
|
+
},
|
|
125
|
+
has_table: {
|
|
126
|
+
type: 'boolean',
|
|
127
|
+
description: 'Only sections with tables',
|
|
128
|
+
},
|
|
129
|
+
limit: {
|
|
130
|
+
type: 'number',
|
|
131
|
+
description: 'Maximum results (default: 20)',
|
|
132
|
+
default: 20,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'md_index',
|
|
139
|
+
description:
|
|
140
|
+
'Build or rebuild the index for a directory. Required before using search tools.',
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
path: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
description: 'Directory to index (default: current directory)',
|
|
147
|
+
default: '.',
|
|
148
|
+
},
|
|
149
|
+
force: {
|
|
150
|
+
type: 'boolean',
|
|
151
|
+
description: 'Force full rebuild (default: false)',
|
|
152
|
+
default: false,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Tool Handlers
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
const handleMdSearch = async (
|
|
164
|
+
args: Record<string, unknown>,
|
|
165
|
+
rootPath: string,
|
|
166
|
+
): Promise<CallToolResult> => {
|
|
167
|
+
const query = args.query as string
|
|
168
|
+
const limit = (args.limit as number) ?? 5
|
|
169
|
+
const pathFilter = args.path_filter as string | undefined
|
|
170
|
+
const threshold = (args.threshold as number) ?? 0.5
|
|
171
|
+
|
|
172
|
+
const result = await Effect.runPromise(
|
|
173
|
+
semanticSearch(rootPath, query, {
|
|
174
|
+
limit,
|
|
175
|
+
threshold,
|
|
176
|
+
pathPattern: pathFilter,
|
|
177
|
+
}).pipe(
|
|
178
|
+
Effect.catchAll((e) => Effect.succeed([{ error: e.message }] as const)),
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if (Array.isArray(result) && result.length > 0 && 'error' in result[0]) {
|
|
183
|
+
return {
|
|
184
|
+
content: [
|
|
185
|
+
{
|
|
186
|
+
type: 'text',
|
|
187
|
+
text: `Error: ${(result[0] as { error: string }).error}`,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
isError: true,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const formattedResults = (
|
|
195
|
+
result as Array<{
|
|
196
|
+
sectionId: string
|
|
197
|
+
documentPath: string
|
|
198
|
+
heading: string
|
|
199
|
+
similarity: number
|
|
200
|
+
}>
|
|
201
|
+
).map((r, i) => {
|
|
202
|
+
const similarity = (r.similarity * 100).toFixed(1)
|
|
203
|
+
return `${i + 1}. **${r.heading}** (${similarity}% match)\n ${r.documentPath}`
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
content: [
|
|
208
|
+
{
|
|
209
|
+
type: 'text',
|
|
210
|
+
text:
|
|
211
|
+
formattedResults.length > 0
|
|
212
|
+
? `Found ${formattedResults.length} results for "${query}":\n\n${formattedResults.join('\n\n')}`
|
|
213
|
+
: `No results found for "${query}"`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const handleMdContext = async (
|
|
220
|
+
args: Record<string, unknown>,
|
|
221
|
+
rootPath: string,
|
|
222
|
+
): Promise<CallToolResult> => {
|
|
223
|
+
const filePath = args.path as string
|
|
224
|
+
const level = (args.level as 'brief' | 'summary' | 'full') ?? 'summary'
|
|
225
|
+
const maxTokens = args.max_tokens as number | undefined
|
|
226
|
+
|
|
227
|
+
const resolvedPath = path.isAbsolute(filePath)
|
|
228
|
+
? filePath
|
|
229
|
+
: path.join(rootPath, filePath)
|
|
230
|
+
|
|
231
|
+
const result = await Effect.runPromise(
|
|
232
|
+
summarizeFile(resolvedPath, { level, maxTokens }).pipe(
|
|
233
|
+
Effect.catchAll((e) => Effect.succeed({ error: e.message })),
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if ('error' in result) {
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
240
|
+
isError: true,
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
content: [{ type: 'text', text: formatSummary(result) }],
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const handleMdStructure = async (
|
|
250
|
+
args: Record<string, unknown>,
|
|
251
|
+
rootPath: string,
|
|
252
|
+
): Promise<CallToolResult> => {
|
|
253
|
+
const filePath = args.path as string
|
|
254
|
+
const resolvedPath = path.isAbsolute(filePath)
|
|
255
|
+
? filePath
|
|
256
|
+
: path.join(rootPath, filePath)
|
|
257
|
+
|
|
258
|
+
const result = await Effect.runPromise(
|
|
259
|
+
parseFile(resolvedPath).pipe(
|
|
260
|
+
Effect.mapError((e) => new Error(`${e._tag}: ${e.message}`)),
|
|
261
|
+
Effect.catchAll((e) => Effect.succeed({ error: e.message })),
|
|
262
|
+
),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if ('error' in result) {
|
|
266
|
+
return {
|
|
267
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
268
|
+
isError: true,
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const formatSection = (section: MdSection, depth: number = 0): string => {
|
|
273
|
+
const indent = ' '.repeat(depth)
|
|
274
|
+
const marker = '#'.repeat(section.level)
|
|
275
|
+
const meta: string[] = []
|
|
276
|
+
if (section.metadata.hasCode) meta.push('code')
|
|
277
|
+
if (section.metadata.hasList) meta.push('list')
|
|
278
|
+
if (section.metadata.hasTable) meta.push('table')
|
|
279
|
+
const metaStr = meta.length > 0 ? ` [${meta.join(', ')}]` : ''
|
|
280
|
+
|
|
281
|
+
let output = `${indent}${marker} ${section.heading}${metaStr} (${section.metadata.tokenCount} tokens)\n`
|
|
282
|
+
|
|
283
|
+
for (const child of section.children) {
|
|
284
|
+
output += formatSection(child, depth + 1)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return output
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const structure = result.sections.map((s) => formatSection(s)).join('')
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
content: [
|
|
294
|
+
{
|
|
295
|
+
type: 'text',
|
|
296
|
+
text: `# ${result.title}\nPath: ${result.path}\nTotal tokens: ${result.metadata.tokenCount}\n\n${structure}`,
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const handleMdKeywordSearch = async (
|
|
303
|
+
args: Record<string, unknown>,
|
|
304
|
+
rootPath: string,
|
|
305
|
+
): Promise<CallToolResult> => {
|
|
306
|
+
const heading = args.heading as string | undefined
|
|
307
|
+
const pathFilter = args.path_filter as string | undefined
|
|
308
|
+
const hasCode = args.has_code as boolean | undefined
|
|
309
|
+
const hasList = args.has_list as boolean | undefined
|
|
310
|
+
const hasTable = args.has_table as boolean | undefined
|
|
311
|
+
const limit = (args.limit as number) ?? 20
|
|
312
|
+
|
|
313
|
+
const result = await Effect.runPromise(
|
|
314
|
+
search(rootPath, {
|
|
315
|
+
heading,
|
|
316
|
+
pathPattern: pathFilter,
|
|
317
|
+
hasCode,
|
|
318
|
+
hasList,
|
|
319
|
+
hasTable,
|
|
320
|
+
limit,
|
|
321
|
+
}).pipe(
|
|
322
|
+
Effect.catchAll((e) => Effect.succeed([{ error: e.message }] as const)),
|
|
323
|
+
),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if (Array.isArray(result) && result.length > 0 && 'error' in result[0]) {
|
|
327
|
+
return {
|
|
328
|
+
content: [
|
|
329
|
+
{
|
|
330
|
+
type: 'text',
|
|
331
|
+
text: `Error: ${(result[0] as { error: string }).error}`,
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
isError: true,
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const formattedResults = (
|
|
339
|
+
result as Array<{
|
|
340
|
+
section: {
|
|
341
|
+
heading: string
|
|
342
|
+
level: number
|
|
343
|
+
documentPath: string
|
|
344
|
+
tokenCount: number
|
|
345
|
+
hasCode: boolean
|
|
346
|
+
hasList: boolean
|
|
347
|
+
hasTable: boolean
|
|
348
|
+
}
|
|
349
|
+
}>
|
|
350
|
+
).map((r, i) => {
|
|
351
|
+
const meta: string[] = []
|
|
352
|
+
if (r.section.hasCode) meta.push('code')
|
|
353
|
+
if (r.section.hasList) meta.push('list')
|
|
354
|
+
if (r.section.hasTable) meta.push('table')
|
|
355
|
+
const metaStr = meta.length > 0 ? ` [${meta.join(', ')}]` : ''
|
|
356
|
+
|
|
357
|
+
return `${i + 1}. **${r.section.heading}**${metaStr}\n ${r.section.documentPath} (${r.section.tokenCount} tokens)`
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
content: [
|
|
362
|
+
{
|
|
363
|
+
type: 'text',
|
|
364
|
+
text:
|
|
365
|
+
formattedResults.length > 0
|
|
366
|
+
? `Found ${formattedResults.length} sections:\n\n${formattedResults.join('\n\n')}`
|
|
367
|
+
: 'No sections found matching criteria',
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const handleMdIndex = async (
|
|
374
|
+
args: Record<string, unknown>,
|
|
375
|
+
rootPath: string,
|
|
376
|
+
): Promise<CallToolResult> => {
|
|
377
|
+
const indexPath = (args.path as string) ?? '.'
|
|
378
|
+
const force = (args.force as boolean) ?? false
|
|
379
|
+
|
|
380
|
+
const resolvedPath = path.isAbsolute(indexPath)
|
|
381
|
+
? indexPath
|
|
382
|
+
: path.join(rootPath, indexPath)
|
|
383
|
+
|
|
384
|
+
const result = await Effect.runPromise(
|
|
385
|
+
buildIndex(resolvedPath, { force }).pipe(
|
|
386
|
+
Effect.catchAll((e) => Effect.succeed({ error: e.message })),
|
|
387
|
+
),
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
if ('error' in result) {
|
|
391
|
+
return {
|
|
392
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
393
|
+
isError: true,
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
content: [
|
|
399
|
+
{
|
|
400
|
+
type: 'text',
|
|
401
|
+
text: `Indexed ${result.documentsIndexed} documents, ${result.sectionsIndexed} sections, ${result.linksIndexed} links in ${result.duration}ms`,
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ============================================================================
|
|
408
|
+
// MCP Server Setup
|
|
409
|
+
// ============================================================================
|
|
410
|
+
|
|
411
|
+
const createServer = (rootPath: string) => {
|
|
412
|
+
const server = new Server(
|
|
413
|
+
{
|
|
414
|
+
name: 'mdcontext-mcp',
|
|
415
|
+
version: '0.1.0',
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
capabilities: {
|
|
419
|
+
tools: {},
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
// List available tools
|
|
425
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
426
|
+
tools,
|
|
427
|
+
}))
|
|
428
|
+
|
|
429
|
+
// Handle tool calls
|
|
430
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
431
|
+
const { name, arguments: args } = request.params
|
|
432
|
+
|
|
433
|
+
switch (name) {
|
|
434
|
+
case 'md_search':
|
|
435
|
+
return handleMdSearch(args ?? {}, rootPath)
|
|
436
|
+
case 'md_context':
|
|
437
|
+
return handleMdContext(args ?? {}, rootPath)
|
|
438
|
+
case 'md_structure':
|
|
439
|
+
return handleMdStructure(args ?? {}, rootPath)
|
|
440
|
+
case 'md_keyword_search':
|
|
441
|
+
return handleMdKeywordSearch(args ?? {}, rootPath)
|
|
442
|
+
case 'md_index':
|
|
443
|
+
return handleMdIndex(args ?? {}, rootPath)
|
|
444
|
+
default:
|
|
445
|
+
return {
|
|
446
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
447
|
+
isError: true,
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
return server
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// Main Entry
|
|
457
|
+
// ============================================================================
|
|
458
|
+
|
|
459
|
+
const main = async () => {
|
|
460
|
+
// Use current working directory as root
|
|
461
|
+
const rootPath = process.cwd()
|
|
462
|
+
|
|
463
|
+
const server = createServer(rootPath)
|
|
464
|
+
const transport = new StdioServerTransport()
|
|
465
|
+
|
|
466
|
+
await server.connect(transport)
|
|
467
|
+
|
|
468
|
+
// Handle graceful shutdown
|
|
469
|
+
process.on('SIGINT', async () => {
|
|
470
|
+
await server.close()
|
|
471
|
+
process.exit(0)
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
process.on('SIGTERM', async () => {
|
|
475
|
+
await server.close()
|
|
476
|
+
process.exit(0)
|
|
477
|
+
})
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
main().catch((error) => {
|
|
481
|
+
console.error('Fatal error:', error)
|
|
482
|
+
process.exit(1)
|
|
483
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './parser.js'
|