|
1 | 1 | #!/usr/bin/env node |
2 | | -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; |
3 | | -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; |
4 | | -import { |
5 | | - CallToolRequestSchema, |
6 | | - ErrorCode, |
7 | | - ListToolsRequestSchema, |
8 | | - McpError, |
9 | | - CallToolRequest, |
10 | | -} from '@modelcontextprotocol/sdk/types.js'; |
11 | | -import path from 'path'; |
12 | | -import { stat } from 'node:fs/promises'; |
13 | | - |
14 | | -// Import from local files |
15 | | -import { ContextManager } from './core.js'; |
16 | | -import { ContextGenerator } from './lib/ContextGenerator.js'; |
17 | | - |
18 | | -interface InitArgs { |
19 | | - path: string; |
20 | | -} |
21 | | - |
22 | | -interface ValidateArgs { |
23 | | - path: string; |
24 | | -} |
25 | | - |
26 | | -interface ContextArgs { |
27 | | - path: string; |
28 | | - raw?: boolean; |
29 | | -} |
30 | | - |
31 | | -interface DiagramsArgs { |
32 | | - path: string; |
33 | | - content?: boolean; |
34 | | -} |
35 | | - |
36 | | -const isInitArgs = (args: unknown): args is InitArgs => |
37 | | - typeof args === 'object' && |
38 | | - args !== null && |
39 | | - (typeof (args as InitArgs).path === 'string' || typeof (args as InitArgs).path === 'undefined'); |
40 | | - |
41 | | -const isValidateArgs = (args: unknown): args is ValidateArgs => |
42 | | - typeof args === 'object' && |
43 | | - args !== null && |
44 | | - (typeof (args as ValidateArgs).path === 'string' || typeof (args as ValidateArgs).path === 'undefined'); |
45 | | - |
46 | | -const isContextArgs = (args: unknown): args is ContextArgs => |
47 | | - typeof args === 'object' && |
48 | | - args !== null && |
49 | | - (typeof (args as ContextArgs).path === 'string' || typeof (args as ContextArgs).path === 'undefined') && |
50 | | - (typeof (args as ContextArgs).raw === 'undefined' || |
51 | | - typeof (args as ContextArgs).raw === 'boolean'); |
52 | | - |
53 | | -const isDiagramsArgs = (args: unknown): args is DiagramsArgs => |
54 | | - typeof args === 'object' && |
55 | | - args !== null && |
56 | | - (typeof (args as DiagramsArgs).path === 'string' || typeof (args as DiagramsArgs).path === 'undefined') && |
57 | | - (typeof (args as DiagramsArgs).content === 'undefined' || |
58 | | - typeof (args as DiagramsArgs).content === 'boolean'); |
59 | | - |
60 | | -class DotContextServer { |
61 | | - private server: Server; |
62 | | - private contextManager: ContextManager; |
63 | | - private ContextGenerator: typeof ContextGenerator; |
64 | | - |
65 | | - constructor() { |
66 | | - this.server = new Server( |
67 | | - { |
68 | | - name: 'dotcontext', |
69 | | - version: '1.3.1', // Match package.json version |
70 | | - }, |
71 | | - { |
72 | | - capabilities: { |
73 | | - tools: {}, |
74 | | - }, |
75 | | - } |
76 | | - ); |
77 | | - |
78 | | - // Use process.cwd() by default |
79 | | - this.contextManager = new ContextManager(); |
80 | | - this.ContextGenerator = ContextGenerator; |
81 | | - |
82 | | - this.setupToolHandlers(); |
83 | | - |
84 | | - // Error handling |
85 | | - this.server.onerror = (error: Error) => { |
86 | | - // Only log actual errors, not debug info |
87 | | - if (error instanceof McpError) { |
88 | | - console.error('[MCP Error]', error.message); |
89 | | - } |
90 | | - }; |
91 | | - process.on('SIGINT', async () => { |
92 | | - await this.server.close(); |
93 | | - process.exit(0); |
94 | | - }); |
95 | | - } |
96 | | - |
97 | | - private setupToolHandlers() { |
98 | | - this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ |
99 | | - tools: [ |
100 | | - { |
101 | | - name: 'init', |
102 | | - description: 'Initialize new context directory and ignore file', |
103 | | - inputSchema: { |
104 | | - type: 'object', |
105 | | - properties: { |
106 | | - path: { |
107 | | - type: 'string', |
108 | | - description: 'Directory path where to initialize .context (defaults to .context in current directory)', |
109 | | - default: '.context' |
110 | | - }, |
111 | | - }, |
112 | | - required: ['path'], |
113 | | - }, |
114 | | - }, |
115 | | - { |
116 | | - name: 'validate', |
117 | | - description: 'Validate a .context directory structure and contents', |
118 | | - inputSchema: { |
119 | | - type: 'object', |
120 | | - properties: { |
121 | | - path: { |
122 | | - type: 'string', |
123 | | - description: 'Path to the .context directory (defaults to .context in current directory)', |
124 | | - default: '.context' |
125 | | - }, |
126 | | - }, |
127 | | - required: ['path'], |
128 | | - }, |
129 | | - }, |
130 | | - { |
131 | | - name: 'context', |
132 | | - description: 'Get context information from index.md including related modules', |
133 | | - inputSchema: { |
134 | | - type: 'object', |
135 | | - properties: { |
136 | | - path: { |
137 | | - type: 'string', |
138 | | - description: 'Path to the .context directory (defaults to .context in current directory)', |
139 | | - default: '.context' |
140 | | - }, |
141 | | - raw: { |
142 | | - type: 'boolean', |
143 | | - description: 'Output raw JSON instead of formatted text', |
144 | | - default: false, |
145 | | - }, |
146 | | - }, |
147 | | - required: ['path'], |
148 | | - }, |
149 | | - }, |
150 | | - { |
151 | | - name: 'diagrams', |
152 | | - description: 'List available Mermaid diagrams', |
153 | | - inputSchema: { |
154 | | - type: 'object', |
155 | | - properties: { |
156 | | - path: { |
157 | | - type: 'string', |
158 | | - description: 'Path to the .context directory (defaults to .context in current directory)', |
159 | | - default: '.context' |
160 | | - }, |
161 | | - content: { |
162 | | - type: 'boolean', |
163 | | - description: 'Include diagram content', |
164 | | - default: false, |
165 | | - }, |
166 | | - }, |
167 | | - required: ['path'], |
168 | | - }, |
169 | | - }, |
170 | | - ], |
171 | | - })); |
172 | | - |
173 | | - this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { |
174 | | - const { name, arguments: args } = request.params; |
175 | | - |
176 | | - try { |
177 | | - switch (name) { |
178 | | - case 'init': { |
179 | | - if (!isInitArgs(args)) { |
180 | | - throw new McpError(ErrorCode.InvalidParams, 'Invalid init arguments'); |
181 | | - } |
182 | | - const contextPath = args.path || '.context'; |
183 | | - const generator = new this.ContextGenerator(contextPath); |
184 | | - const result = await generator.generate(); |
185 | | - |
186 | | - return { |
187 | | - content: [ |
188 | | - { |
189 | | - type: 'text', |
190 | | - text: JSON.stringify({ |
191 | | - success: true, |
192 | | - dirCreated: result.dirCreated, |
193 | | - indexCreated: result.indexCreated, |
194 | | - ignoreCreated: result.ignoreCreated, |
195 | | - }, null, 2), |
196 | | - }, |
197 | | - ], |
198 | | - }; |
199 | | - } |
200 | | - |
201 | | - case 'validate': { |
202 | | - if (!isValidateArgs(args)) { |
203 | | - throw new McpError(ErrorCode.InvalidParams, 'Invalid validate arguments'); |
204 | | - } |
205 | | - const contextPath = args.path || '.context'; |
206 | | - const result = await this.contextManager.validateContextStructure(contextPath); |
207 | | - |
208 | | - return { |
209 | | - content: [ |
210 | | - { |
211 | | - type: 'text', |
212 | | - text: JSON.stringify({ |
213 | | - valid: result.valid, |
214 | | - errors: result.errors, |
215 | | - }, null, 2), |
216 | | - }, |
217 | | - ], |
218 | | - }; |
219 | | - } |
220 | | - |
221 | | - case 'context': { |
222 | | - if (!isContextArgs(args)) { |
223 | | - throw new McpError(ErrorCode.InvalidParams, 'Invalid context arguments'); |
224 | | - } |
225 | | - const contextPath = args.path || '.context'; |
226 | | - const context = await this.contextManager.getModuleContext(contextPath); |
227 | | - |
228 | | - if (args.raw) { |
229 | | - return { |
230 | | - content: [ |
231 | | - { |
232 | | - type: 'text', |
233 | | - text: JSON.stringify(context, null, 2), |
234 | | - }, |
235 | | - ], |
236 | | - }; |
237 | | - } |
238 | | - |
239 | | - // Format context information |
240 | | - let formattedText = `\n📖 Module: ${context.metadata['module-name']}\n`; |
241 | | - formattedText += `\nDescription: ${context.metadata.description}\n`; |
242 | | - formattedText += `\n🏗️ Architecture:\n`; |
243 | | - formattedText += `Style: ${context.metadata.architecture.style}\n`; |
244 | | - formattedText += `\nComponents:\n`; |
245 | | - context.metadata.architecture.components.forEach((comp: any) => { |
246 | | - formattedText += ` - ${comp.name}: ${comp.description}\n`; |
247 | | - }); |
248 | | - formattedText += `\nPatterns:\n`; |
249 | | - context.metadata.architecture.patterns.forEach((pattern: any) => { |
250 | | - formattedText += ` - ${pattern.name}: ${pattern.usage}\n`; |
251 | | - }); |
252 | | - |
253 | | - if (context.relatedModules.length > 0) { |
254 | | - formattedText += `\n🔗 Related Modules:\n`; |
255 | | - context.relatedModules.forEach((module: any) => { |
256 | | - formattedText += ` - ${module.name} (${module.path})\n`; |
257 | | - if (module.error) { |
258 | | - formattedText += ` ⚠️ Error: ${module.error}\n`; |
259 | | - } |
260 | | - }); |
261 | | - } |
262 | | - |
263 | | - if (Object.keys(context.diagrams).length > 0) { |
264 | | - formattedText += `\n📊 Diagrams:\n`; |
265 | | - Object.keys(context.diagrams).forEach(diagram => { |
266 | | - formattedText += ` - ${diagram}\n`; |
267 | | - }); |
268 | | - } |
269 | | - |
270 | | - return { |
271 | | - content: [ |
272 | | - { |
273 | | - type: 'text', |
274 | | - text: formattedText, |
275 | | - }, |
276 | | - ], |
277 | | - }; |
278 | | - } |
279 | | - |
280 | | - case 'diagrams': { |
281 | | - if (!isDiagramsArgs(args)) { |
282 | | - throw new McpError( |
283 | | - ErrorCode.InvalidParams, |
284 | | - 'Invalid diagrams arguments: path must be a string and content (if provided) must be a boolean' |
285 | | - ); |
286 | | - } |
287 | | - const contextPath = args.path || '.context'; |
288 | | - const diagrams = await this.contextManager.getDiagrams(contextPath); |
289 | | - |
290 | | - let formattedText = ''; |
291 | | - if (diagrams.length === 0) { |
292 | | - formattedText = 'No diagrams found'; |
293 | | - } else { |
294 | | - formattedText = '\n📊 Available diagrams:\n'; |
295 | | - for (const diagram of diagrams) { |
296 | | - formattedText += ` - ${diagram}\n`; |
297 | | - if (args.content) { |
298 | | - const context = await this.contextManager.getModuleContext(contextPath); |
299 | | - if (context.diagrams[diagram]) { |
300 | | - formattedText += '\nContent:\n'; |
301 | | - formattedText += context.diagrams[diagram]; |
302 | | - formattedText += '\n'; |
303 | | - } |
304 | | - } |
305 | | - } |
306 | | - } |
307 | | - |
308 | | - return { |
309 | | - content: [ |
310 | | - { |
311 | | - type: 'text', |
312 | | - text: formattedText, |
313 | | - }, |
314 | | - ], |
315 | | - }; |
316 | | - } |
317 | | - |
318 | | - default: |
319 | | - throw new McpError( |
320 | | - ErrorCode.MethodNotFound, |
321 | | - `Unknown tool: ${name}` |
322 | | - ); |
323 | | - } |
324 | | - } catch (error) { |
325 | | - return { |
326 | | - content: [ |
327 | | - { |
328 | | - type: 'text', |
329 | | - text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`, |
330 | | - }, |
331 | | - ], |
332 | | - isError: true, |
333 | | - }; |
334 | | - } |
335 | | - }); |
336 | | - } |
337 | | - |
338 | | - async run() { |
339 | | - const transport = new StdioServerTransport(); |
340 | | - await this.server.connect(transport); |
341 | | - } |
342 | | -} |
343 | | - |
344 | | -const server = new DotContextServer(); |
345 | | -server.run().catch(console.error); |
| 2 | +export * from './mcp/index.js'; |
0 commit comments