@contextos/mcp 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/index.js ADDED
@@ -0,0 +1,741 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema,
9
+ ListResourcesRequestSchema,
10
+ ReadResourceRequestSchema,
11
+ ListPromptsRequestSchema,
12
+ GetPromptRequestSchema
13
+ } from "@modelcontextprotocol/sdk/types.js";
14
+
15
+ // src/provider.ts
16
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
17
+ import { join, relative } from "path";
18
+ var core = null;
19
+ async function loadCore() {
20
+ if (core) return core;
21
+ try {
22
+ core = await import("@contextos/core");
23
+ return core;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ var ContextOSProvider = class {
29
+ projectDir;
30
+ contextDir;
31
+ lastContext = "";
32
+ constructor(projectDir) {
33
+ this.projectDir = projectDir || process.cwd();
34
+ this.contextDir = join(this.projectDir, ".contextos");
35
+ }
36
+ /**
37
+ * Check if ContextOS is initialized in the current directory
38
+ */
39
+ isInitialized() {
40
+ return existsSync(this.contextDir);
41
+ }
42
+ /**
43
+ * Build optimized context for a goal
44
+ */
45
+ async buildContext(goal) {
46
+ const core2 = await loadCore();
47
+ if (core2) {
48
+ try {
49
+ const builder = await core2.getContextBuilder();
50
+ const result = await builder.build({ goal, maxTokens: 32e3 });
51
+ this.lastContext = result.context;
52
+ return this.formatContext(goal, result);
53
+ } catch (error) {
54
+ }
55
+ }
56
+ return this.buildSimpleContext(goal);
57
+ }
58
+ /**
59
+ * Analyze codebase with RLM
60
+ */
61
+ async analyze(query) {
62
+ const core2 = await loadCore();
63
+ if (core2) {
64
+ try {
65
+ const engine = new core2.RLMEngine({
66
+ maxDepth: 3,
67
+ maxTokenBudget: 5e4
68
+ });
69
+ const result = await engine.execute(query, await this.getCurrentContext());
70
+ return result.answer;
71
+ } catch (error) {
72
+ return `Analysis requires AI API key. Error: ${error instanceof Error ? error.message : String(error)}`;
73
+ }
74
+ }
75
+ return "Full analysis requires @contextos/core with AI API key configured.";
76
+ }
77
+ /**
78
+ * Find files matching pattern
79
+ */
80
+ async findFiles(pattern) {
81
+ const files = this.walkDirectory(this.projectDir, pattern);
82
+ if (files.length === 0) {
83
+ return `No files found matching pattern: ${pattern}`;
84
+ }
85
+ return `# Files matching "${pattern}"
86
+
87
+ ${files.map((f) => `- ${f}`).join("\n")}`;
88
+ }
89
+ /**
90
+ * Get dependencies of a file
91
+ */
92
+ async getDependencies(file, depth = 2) {
93
+ const core2 = await loadCore();
94
+ if (core2) {
95
+ try {
96
+ const fullPath = join(this.projectDir, file);
97
+ if (!existsSync(fullPath)) {
98
+ return `File not found: ${file}`;
99
+ }
100
+ const content = readFileSync(fullPath, "utf-8");
101
+ const result = core2.parseWithRegex(content, this.detectLanguage(file));
102
+ const deps = result.imports.map((i) => i.source);
103
+ return `# Dependencies of ${file}
104
+
105
+ ${deps.map((d) => `- ${d}`).join("\n") || "No imports found"}`;
106
+ } catch (error) {
107
+ }
108
+ }
109
+ return this.extractImportsSimple(file);
110
+ }
111
+ /**
112
+ * Explain a file
113
+ */
114
+ async explainFile(file) {
115
+ const fullPath = join(this.projectDir, file);
116
+ if (!existsSync(fullPath)) {
117
+ return `File not found: ${file}`;
118
+ }
119
+ const content = readFileSync(fullPath, "utf-8");
120
+ const lines = content.split("\n").length;
121
+ const core2 = await loadCore();
122
+ let analysis = "";
123
+ if (core2) {
124
+ try {
125
+ const result = core2.parseWithRegex(content, this.detectLanguage(file));
126
+ analysis = `
127
+ ## Structure
128
+
129
+ - **Functions**: ${result.functions.join(", ") || "None"}
130
+ - **Classes**: ${result.classes.join(", ") || "None"}
131
+ - **Imports**: ${result.imports.length} imports
132
+ `;
133
+ } catch {
134
+ }
135
+ }
136
+ return `# ${file}
137
+
138
+ ## Overview
139
+
140
+ - **Lines**: ${lines}
141
+ - **Language**: ${this.detectLanguage(file)}
142
+ ${analysis}
143
+
144
+ ## Content Preview
145
+
146
+ \`\`\`${this.detectLanguage(file)}
147
+ ${content.slice(0, 2e3)}${content.length > 2e3 ? "\n... (truncated)" : ""}
148
+ \`\`\`
149
+ `;
150
+ }
151
+ /**
152
+ * Get current status
153
+ */
154
+ async getStatus() {
155
+ const initialized = this.isInitialized();
156
+ let status = `# ContextOS Status
157
+
158
+ `;
159
+ status += `- **Project Directory**: ${this.projectDir}
160
+ `;
161
+ status += `- **Initialized**: ${initialized ? "\u2705 Yes" : "\u274C No"}
162
+ `;
163
+ if (initialized) {
164
+ const configPath = join(this.contextDir, "context.yaml");
165
+ if (existsSync(configPath)) {
166
+ status += `- **Config**: context.yaml found
167
+ `;
168
+ }
169
+ } else {
170
+ status += `
171
+ > Run \`ctx init\` to initialize ContextOS in this project.`;
172
+ }
173
+ return status;
174
+ }
175
+ /**
176
+ * Get current context
177
+ */
178
+ async getCurrentContext() {
179
+ if (this.lastContext) {
180
+ return this.lastContext;
181
+ }
182
+ const cachePath = join(this.contextDir, "cache", "last-context.md");
183
+ if (existsSync(cachePath)) {
184
+ return readFileSync(cachePath, "utf-8");
185
+ }
186
+ return "No context built yet. Use contextos_build tool first.";
187
+ }
188
+ /**
189
+ * Get project info
190
+ */
191
+ async getProjectInfo() {
192
+ const configPath = join(this.contextDir, "context.yaml");
193
+ if (!existsSync(configPath)) {
194
+ return JSON.stringify({
195
+ error: "ContextOS not initialized",
196
+ suggestion: "Run ctx init"
197
+ }, null, 2);
198
+ }
199
+ const content = readFileSync(configPath, "utf-8");
200
+ const info = {};
201
+ const lines = content.split("\n");
202
+ for (const line of lines) {
203
+ const match = line.match(/^\s*(name|language|framework|description):\s*["']?([^"'\n]+)["']?/);
204
+ if (match) {
205
+ info[match[1]] = match[2].trim();
206
+ }
207
+ }
208
+ return JSON.stringify(info, null, 2);
209
+ }
210
+ /**
211
+ * Get constraints
212
+ */
213
+ async getConstraints() {
214
+ const configPath = join(this.contextDir, "context.yaml");
215
+ if (!existsSync(configPath)) {
216
+ return "No constraints defined (ContextOS not initialized)";
217
+ }
218
+ const content = readFileSync(configPath, "utf-8");
219
+ const constraintsMatch = content.match(/constraints:\s*\n((?:\s+-[^\n]+\n?)+)/);
220
+ if (!constraintsMatch) {
221
+ return "No constraints defined in context.yaml";
222
+ }
223
+ return `# Project Constraints
224
+
225
+ ${constraintsMatch[1]}`;
226
+ }
227
+ /**
228
+ * Get project structure
229
+ */
230
+ async getProjectStructure() {
231
+ const tree = this.buildTree(this.projectDir, "", 0, 3);
232
+ return `# Project Structure
233
+
234
+ \`\`\`
235
+ ${tree}\`\`\``;
236
+ }
237
+ /**
238
+ * Get file with dependencies
239
+ */
240
+ async getFileWithDeps(file) {
241
+ const content = await this.explainFile(file);
242
+ const deps = await this.getDependencies(file);
243
+ return `${content}
244
+
245
+ ${deps}`;
246
+ }
247
+ // ═══════════════════════════════════════════════════════════
248
+ // PRIVATE HELPERS
249
+ // ═══════════════════════════════════════════════════════════
250
+ formatContext(goal, result) {
251
+ return `# Context for: ${goal}
252
+
253
+ ## Statistics
254
+ - Files included: ${result.files.length}
255
+ - Total tokens: ~${result.tokens}
256
+
257
+ ## Files
258
+ ${result.files.map((f) => `- ${f}`).join("\n")}
259
+
260
+ ## Content
261
+
262
+ ${result.context}
263
+ `;
264
+ }
265
+ async buildSimpleContext(goal) {
266
+ const keywords = goal.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
267
+ const files = this.walkDirectory(this.projectDir);
268
+ const relevant = files.filter((file) => {
269
+ const lower = file.toLowerCase();
270
+ return keywords.some((kw) => lower.includes(kw));
271
+ }).slice(0, 10);
272
+ if (relevant.length === 0) {
273
+ return `No files found related to: ${goal}
274
+
275
+ Try running \`ctx index\` first.`;
276
+ }
277
+ let context = `# Context for: ${goal}
278
+
279
+ `;
280
+ for (const file of relevant) {
281
+ const fullPath = join(this.projectDir, file);
282
+ try {
283
+ const content = readFileSync(fullPath, "utf-8");
284
+ context += `## ${file}
285
+
286
+ \`\`\`
287
+ ${content.slice(0, 3e3)}
288
+ \`\`\`
289
+
290
+ `;
291
+ } catch {
292
+ }
293
+ }
294
+ this.lastContext = context;
295
+ return context;
296
+ }
297
+ walkDirectory(dir, pattern, maxFiles = 100) {
298
+ const results = [];
299
+ const ignored = ["node_modules", ".git", "dist", "build", ".contextos", "coverage"];
300
+ const walk = (currentDir) => {
301
+ if (results.length >= maxFiles) return;
302
+ try {
303
+ const entries = readdirSync(currentDir);
304
+ for (const entry of entries) {
305
+ if (results.length >= maxFiles) break;
306
+ if (ignored.includes(entry)) continue;
307
+ const fullPath = join(currentDir, entry);
308
+ const relativePath = relative(this.projectDir, fullPath);
309
+ try {
310
+ const stat = statSync(fullPath);
311
+ if (stat.isDirectory()) {
312
+ walk(fullPath);
313
+ } else if (stat.isFile()) {
314
+ if (!pattern || this.matchPattern(relativePath, pattern)) {
315
+ results.push(relativePath);
316
+ }
317
+ }
318
+ } catch {
319
+ }
320
+ }
321
+ } catch {
322
+ }
323
+ };
324
+ walk(dir);
325
+ return results;
326
+ }
327
+ matchPattern(path, pattern) {
328
+ const regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
329
+ return new RegExp(regex).test(path);
330
+ }
331
+ detectLanguage(file) {
332
+ const ext = file.split(".").pop()?.toLowerCase() || "";
333
+ const langMap = {
334
+ ts: "typescript",
335
+ tsx: "typescript",
336
+ js: "javascript",
337
+ jsx: "javascript",
338
+ py: "python",
339
+ go: "go",
340
+ rs: "rust",
341
+ java: "java"
342
+ };
343
+ return langMap[ext] || ext;
344
+ }
345
+ extractImportsSimple(file) {
346
+ const fullPath = join(this.projectDir, file);
347
+ if (!existsSync(fullPath)) {
348
+ return `File not found: ${file}`;
349
+ }
350
+ const content = readFileSync(fullPath, "utf-8");
351
+ const imports = [];
352
+ const patterns = [
353
+ /import\s+.*from\s+['"]([^'"]+)['"]/g,
354
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
355
+ /^import\s+([\w.]+)/gm,
356
+ /^from\s+([\w.]+)\s+import/gm
357
+ ];
358
+ for (const pattern of patterns) {
359
+ let match;
360
+ while ((match = pattern.exec(content)) !== null) {
361
+ if (match[1] && !imports.includes(match[1])) {
362
+ imports.push(match[1]);
363
+ }
364
+ }
365
+ }
366
+ return `# Dependencies of ${file}
367
+
368
+ ${imports.map((i) => `- ${i}`).join("\n") || "No imports found"}`;
369
+ }
370
+ buildTree(dir, prefix, depth, maxDepth) {
371
+ if (depth >= maxDepth) return "";
372
+ const ignored = ["node_modules", ".git", "dist", "build", ".contextos", "coverage", "__pycache__"];
373
+ let result = "";
374
+ try {
375
+ const entries = readdirSync(dir).filter((e) => !ignored.includes(e)).sort();
376
+ for (let i = 0; i < entries.length && i < 20; i++) {
377
+ const entry = entries[i];
378
+ const isLast = i === entries.length - 1 || i === 19;
379
+ const fullPath = join(dir, entry);
380
+ try {
381
+ const stat = statSync(fullPath);
382
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
383
+ result += `${prefix}${connector}${entry}${stat.isDirectory() ? "/" : ""}
384
+ `;
385
+ if (stat.isDirectory()) {
386
+ const newPrefix = prefix + (isLast ? " " : "\u2502 ");
387
+ result += this.buildTree(fullPath, newPrefix, depth + 1, maxDepth);
388
+ }
389
+ } catch {
390
+ }
391
+ }
392
+ if (entries.length > 20) {
393
+ result += `${prefix}\u2514\u2500\u2500 ... (${entries.length - 20} more)
394
+ `;
395
+ }
396
+ } catch {
397
+ }
398
+ return result;
399
+ }
400
+ };
401
+
402
+ // src/definitions.ts
403
+ var TOOLS = [
404
+ {
405
+ name: "contextos_build",
406
+ description: "Build optimized context for a specific goal. Returns the most relevant files from the codebase based on semantic similarity, dependency graph, and custom rules.",
407
+ inputSchema: {
408
+ type: "object",
409
+ properties: {
410
+ goal: {
411
+ type: "string",
412
+ description: 'The coding task or goal (e.g., "Add authentication to UserController")'
413
+ }
414
+ },
415
+ required: ["goal"]
416
+ }
417
+ },
418
+ {
419
+ name: "contextos_analyze",
420
+ description: "Perform deep analysis of the codebase using RLM engine. Can find patterns, security issues, or answer complex questions about the code.",
421
+ inputSchema: {
422
+ type: "object",
423
+ properties: {
424
+ query: {
425
+ type: "string",
426
+ description: 'Analysis query (e.g., "Find potential security vulnerabilities")'
427
+ }
428
+ },
429
+ required: ["query"]
430
+ }
431
+ },
432
+ {
433
+ name: "contextos_find",
434
+ description: "Find files matching a pattern in the indexed codebase.",
435
+ inputSchema: {
436
+ type: "object",
437
+ properties: {
438
+ pattern: {
439
+ type: "string",
440
+ description: 'Glob pattern to match (e.g., "**/auth/**/*.ts")'
441
+ }
442
+ },
443
+ required: ["pattern"]
444
+ }
445
+ },
446
+ {
447
+ name: "contextos_deps",
448
+ description: "Get dependencies of a file up to a specified depth.",
449
+ inputSchema: {
450
+ type: "object",
451
+ properties: {
452
+ file: {
453
+ type: "string",
454
+ description: "File path to analyze dependencies for"
455
+ },
456
+ depth: {
457
+ type: "number",
458
+ description: "Maximum depth to traverse (default: 2)"
459
+ }
460
+ },
461
+ required: ["file"]
462
+ }
463
+ },
464
+ {
465
+ name: "contextos_explain",
466
+ description: "Get an AI-powered explanation of a file, including its purpose, key functions, and how it relates to other parts of the codebase.",
467
+ inputSchema: {
468
+ type: "object",
469
+ properties: {
470
+ file: {
471
+ type: "string",
472
+ description: "File path to explain"
473
+ }
474
+ },
475
+ required: ["file"]
476
+ }
477
+ },
478
+ {
479
+ name: "contextos_status",
480
+ description: "Get the current status of ContextOS: project info, index status, and configuration.",
481
+ inputSchema: {
482
+ type: "object",
483
+ properties: {}
484
+ }
485
+ }
486
+ ];
487
+ var RESOURCES = [
488
+ {
489
+ uri: "contextos://context/current",
490
+ name: "Current Context",
491
+ description: "The most recently built context (output of ctx build/goal)",
492
+ mimeType: "text/markdown"
493
+ },
494
+ {
495
+ uri: "contextos://project/info",
496
+ name: "Project Info",
497
+ description: "Project configuration from context.yaml (name, language, framework, stack)",
498
+ mimeType: "application/json"
499
+ },
500
+ {
501
+ uri: "contextos://project/constraints",
502
+ name: "Coding Constraints",
503
+ description: "Project coding rules and constraints that should be followed",
504
+ mimeType: "text/markdown"
505
+ },
506
+ {
507
+ uri: "contextos://project/structure",
508
+ name: "Project Structure",
509
+ description: "Directory tree of the project (excluding node_modules, etc.)",
510
+ mimeType: "text/plain"
511
+ }
512
+ ];
513
+ var PROMPTS = [
514
+ {
515
+ name: "code_with_context",
516
+ description: "Start a coding task with optimized context from ContextOS",
517
+ arguments: [
518
+ {
519
+ name: "goal",
520
+ description: "The coding task or goal",
521
+ required: true
522
+ }
523
+ ]
524
+ },
525
+ {
526
+ name: "review_code",
527
+ description: "Review a file and its dependencies",
528
+ arguments: [
529
+ {
530
+ name: "file",
531
+ description: "File path to review",
532
+ required: true
533
+ }
534
+ ]
535
+ },
536
+ {
537
+ name: "debug_issue",
538
+ description: "Debug an issue with relevant context",
539
+ arguments: [
540
+ {
541
+ name: "issue",
542
+ description: "Description of the issue",
543
+ required: true
544
+ }
545
+ ]
546
+ }
547
+ ];
548
+
549
+ // src/index.ts
550
+ var server = new Server(
551
+ {
552
+ name: "contextos",
553
+ version: "0.1.0"
554
+ },
555
+ {
556
+ capabilities: {
557
+ tools: {},
558
+ resources: {},
559
+ prompts: {}
560
+ }
561
+ }
562
+ );
563
+ var provider = new ContextOSProvider();
564
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
565
+ tools: TOOLS
566
+ }));
567
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
568
+ const { name, arguments: args } = request.params;
569
+ try {
570
+ switch (name) {
571
+ case "contextos_build": {
572
+ const goal = args.goal;
573
+ const result = await provider.buildContext(goal);
574
+ return {
575
+ content: [{ type: "text", text: result }]
576
+ };
577
+ }
578
+ case "contextos_analyze": {
579
+ const query = args.query;
580
+ const result = await provider.analyze(query);
581
+ return {
582
+ content: [{ type: "text", text: result }]
583
+ };
584
+ }
585
+ case "contextos_find": {
586
+ const pattern = args.pattern;
587
+ const result = await provider.findFiles(pattern);
588
+ return {
589
+ content: [{ type: "text", text: result }]
590
+ };
591
+ }
592
+ case "contextos_deps": {
593
+ const file = args.file;
594
+ const depth = args.depth || 2;
595
+ const result = await provider.getDependencies(file, depth);
596
+ return {
597
+ content: [{ type: "text", text: result }]
598
+ };
599
+ }
600
+ case "contextos_explain": {
601
+ const file = args.file;
602
+ const result = await provider.explainFile(file);
603
+ return {
604
+ content: [{ type: "text", text: result }]
605
+ };
606
+ }
607
+ case "contextos_status": {
608
+ const result = await provider.getStatus();
609
+ return {
610
+ content: [{ type: "text", text: result }]
611
+ };
612
+ }
613
+ default:
614
+ throw new Error(`Unknown tool: ${name}`);
615
+ }
616
+ } catch (error) {
617
+ return {
618
+ content: [{
619
+ type: "text",
620
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
621
+ }],
622
+ isError: true
623
+ };
624
+ }
625
+ });
626
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
627
+ resources: RESOURCES
628
+ }));
629
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
630
+ const { uri } = request.params;
631
+ try {
632
+ if (uri === "contextos://context/current") {
633
+ const content = await provider.getCurrentContext();
634
+ return {
635
+ contents: [{ uri, mimeType: "text/markdown", text: content }]
636
+ };
637
+ }
638
+ if (uri === "contextos://project/info") {
639
+ const content = await provider.getProjectInfo();
640
+ return {
641
+ contents: [{ uri, mimeType: "application/json", text: content }]
642
+ };
643
+ }
644
+ if (uri === "contextos://project/constraints") {
645
+ const content = await provider.getConstraints();
646
+ return {
647
+ contents: [{ uri, mimeType: "text/markdown", text: content }]
648
+ };
649
+ }
650
+ if (uri === "contextos://project/structure") {
651
+ const content = await provider.getProjectStructure();
652
+ return {
653
+ contents: [{ uri, mimeType: "text/plain", text: content }]
654
+ };
655
+ }
656
+ throw new Error(`Unknown resource: ${uri}`);
657
+ } catch (error) {
658
+ throw new Error(`Failed to read resource: ${error instanceof Error ? error.message : String(error)}`);
659
+ }
660
+ });
661
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
662
+ prompts: PROMPTS
663
+ }));
664
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
665
+ const { name, arguments: args } = request.params;
666
+ switch (name) {
667
+ case "code_with_context": {
668
+ const goal = args?.goal || "general coding task";
669
+ const context = await provider.buildContext(goal);
670
+ return {
671
+ messages: [
672
+ {
673
+ role: "user",
674
+ content: {
675
+ type: "text",
676
+ text: `# Project Context
677
+
678
+ ${context}
679
+
680
+ # Task
681
+
682
+ ${goal}`
683
+ }
684
+ }
685
+ ]
686
+ };
687
+ }
688
+ case "review_code": {
689
+ const file = args?.file || "";
690
+ const content = await provider.getFileWithDeps(file);
691
+ return {
692
+ messages: [
693
+ {
694
+ role: "user",
695
+ content: {
696
+ type: "text",
697
+ text: `# Code Review Request
698
+
699
+ Please review the following code and its dependencies:
700
+
701
+ ${content}`
702
+ }
703
+ }
704
+ ]
705
+ };
706
+ }
707
+ case "debug_issue": {
708
+ const issue = args?.issue || "unknown issue";
709
+ const context = await provider.buildContext(`debug: ${issue}`);
710
+ return {
711
+ messages: [
712
+ {
713
+ role: "user",
714
+ content: {
715
+ type: "text",
716
+ text: `# Debug Request
717
+
718
+ ## Issue
719
+ ${issue}
720
+
721
+ ## Relevant Context
722
+
723
+ ${context}`
724
+ }
725
+ }
726
+ ]
727
+ };
728
+ }
729
+ default:
730
+ throw new Error(`Unknown prompt: ${name}`);
731
+ }
732
+ });
733
+ async function main() {
734
+ const transport = new StdioServerTransport();
735
+ await server.connect(transport);
736
+ console.error("ContextOS MCP Server started");
737
+ }
738
+ main().catch((error) => {
739
+ console.error("Failed to start server:", error);
740
+ process.exit(1);
741
+ });