smart-coding-mcp 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/index.js ADDED
@@ -0,0 +1,174 @@
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 { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { pipeline } from "@xenova/transformers";
6
+ import fs from "fs/promises";
7
+
8
+ import { loadConfig } from "./lib/config.js";
9
+ import { EmbeddingsCache } from "./lib/cache.js";
10
+ import { CodebaseIndexer } from "./features/index-codebase.js";
11
+ import { HybridSearch } from "./features/hybrid-search.js";
12
+
13
+ import * as IndexCodebaseFeature from "./features/index-codebase.js";
14
+ import * as HybridSearchFeature from "./features/hybrid-search.js";
15
+ import * as ClearCacheFeature from "./features/clear-cache.js";
16
+
17
+ // Parse workspace from command line arguments
18
+ const args = process.argv.slice(2);
19
+ const workspaceIndex = args.findIndex(arg => arg.startsWith('--workspace'));
20
+ let workspaceDir = null;
21
+
22
+ if (workspaceIndex !== -1) {
23
+ const arg = args[workspaceIndex];
24
+ if (arg.includes('=')) {
25
+ workspaceDir = arg.split('=')[1];
26
+ } else if (workspaceIndex + 1 < args.length) {
27
+ workspaceDir = args[workspaceIndex + 1];
28
+ }
29
+
30
+ if (workspaceDir) {
31
+ console.error(`[Server] Workspace mode: ${workspaceDir}`);
32
+ }
33
+ }
34
+
35
+ // Global state
36
+ let embedder = null;
37
+ let cache = null;
38
+ let indexer = null;
39
+ let hybridSearch = null;
40
+ let config = null;
41
+
42
+ // Feature registry
43
+ const features = [
44
+ {
45
+ module: IndexCodebaseFeature,
46
+ instance: null,
47
+ handler: IndexCodebaseFeature.handleToolCall
48
+ },
49
+ {
50
+ module: HybridSearchFeature,
51
+ instance: null,
52
+ handler: HybridSearchFeature.handleToolCall
53
+ },
54
+ {
55
+ module: ClearCacheFeature,
56
+ instance: null,
57
+ handler: ClearCacheFeature.handleToolCall
58
+ }
59
+ ];
60
+
61
+ // Initialize application
62
+ async function initialize() {
63
+ // Load configuration with workspace support
64
+ config = await loadConfig(workspaceDir);
65
+
66
+ // Ensure search directory exists
67
+ try {
68
+ await fs.access(config.searchDirectory);
69
+ } catch {
70
+ console.error(`[Server] Error: Search directory "${config.searchDirectory}" does not exist`);
71
+ process.exit(1);
72
+ }
73
+
74
+ // Load AI model
75
+ console.error("[Server] Loading AI embedding model (this may take time on first run)...");
76
+ embedder = await pipeline("feature-extraction", config.embeddingModel);
77
+
78
+ // Initialize cache
79
+ cache = new EmbeddingsCache(config);
80
+ await cache.load();
81
+
82
+ // Initialize features
83
+ indexer = new CodebaseIndexer(embedder, cache, config);
84
+ hybridSearch = new HybridSearch(embedder, cache, config);
85
+ const cacheClearer = new ClearCacheFeature.CacheClearer(embedder, cache, config);
86
+
87
+ // Store feature instances
88
+ features[0].instance = indexer;
89
+ features[1].instance = hybridSearch;
90
+ features[2].instance = cacheClearer;
91
+
92
+ // Index codebase
93
+ await indexer.initialize();
94
+ }
95
+
96
+ // Setup MCP server
97
+ const server = new Server(
98
+ {
99
+ name: "smart-coding-mcp",
100
+ version: "1.0.0"
101
+ },
102
+ {
103
+ capabilities: {
104
+ tools: {}
105
+ }
106
+ }
107
+ );
108
+
109
+ // Register tools from all features
110
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
111
+ const tools = [];
112
+
113
+ for (const feature of features) {
114
+ const toolDef = feature.module.getToolDefinition(config);
115
+ tools.push(toolDef);
116
+ }
117
+
118
+ return { tools };
119
+ });
120
+
121
+ // Handle tool calls
122
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
123
+ for (const feature of features) {
124
+ const toolDef = feature.module.getToolDefinition(config);
125
+
126
+ if (request.params.name === toolDef.name) {
127
+ return await feature.handler(request, feature.instance);
128
+ }
129
+ }
130
+
131
+ return {
132
+ content: [{
133
+ type: "text",
134
+ text: `Unknown tool: ${request.params.name}`
135
+ }]
136
+ };
137
+ });
138
+
139
+ // Main entry point
140
+ async function main() {
141
+ await initialize();
142
+
143
+ const transport = new StdioServerTransport();
144
+ await server.connect(transport);
145
+
146
+ console.error("[Server] Smart Coding MCP server ready!");
147
+ }
148
+
149
+ // Graceful shutdown
150
+ process.on('SIGINT', async () => {
151
+ console.error("\n[Server] Shutting down gracefully...");
152
+
153
+ // Stop file watcher
154
+ if (indexer && indexer.watcher) {
155
+ await indexer.watcher.close();
156
+ console.error("[Server] File watcher stopped");
157
+ }
158
+
159
+ // Save cache
160
+ if (cache) {
161
+ await cache.save();
162
+ console.error("[Server] Cache saved");
163
+ }
164
+
165
+ console.error("[Server] Goodbye!");
166
+ process.exit(0);
167
+ });
168
+
169
+ process.on('SIGTERM', async () => {
170
+ console.error("\n[Server] Received SIGTERM, shutting down...");
171
+ process.exit(0);
172
+ });
173
+
174
+ main().catch(console.error);
package/lib/cache.js ADDED
@@ -0,0 +1,114 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+
4
+ export class EmbeddingsCache {
5
+ constructor(config) {
6
+ this.config = config;
7
+ this.vectorStore = [];
8
+ this.fileHashes = new Map();
9
+ }
10
+
11
+ async load() {
12
+ if (!this.config.enableCache) return;
13
+
14
+ try {
15
+ await fs.mkdir(this.config.cacheDirectory, { recursive: true });
16
+ const cacheFile = path.join(this.config.cacheDirectory, "embeddings.json");
17
+ const hashFile = path.join(this.config.cacheDirectory, "file-hashes.json");
18
+
19
+ const [cacheData, hashData] = await Promise.all([
20
+ fs.readFile(cacheFile, "utf-8").catch(() => null),
21
+ fs.readFile(hashFile, "utf-8").catch(() => null)
22
+ ]);
23
+
24
+ if (cacheData && hashData) {
25
+ const rawVectorStore = JSON.parse(cacheData);
26
+ const rawHashes = new Map(Object.entries(JSON.parse(hashData)));
27
+
28
+ // Filter cache to only include files matching current extensions
29
+ const allowedExtensions = this.config.fileExtensions.map(ext => `.${ext}`);
30
+
31
+ this.vectorStore = rawVectorStore.filter(chunk => {
32
+ const ext = path.extname(chunk.file);
33
+ return allowedExtensions.includes(ext);
34
+ });
35
+
36
+ // Only keep hashes for files matching current extensions
37
+ for (const [file, hash] of rawHashes) {
38
+ const ext = path.extname(file);
39
+ if (allowedExtensions.includes(ext)) {
40
+ this.fileHashes.set(file, hash);
41
+ }
42
+ }
43
+
44
+ const filtered = rawVectorStore.length - this.vectorStore.length;
45
+ if (filtered > 0) {
46
+ console.error(`[Cache] Filtered ${filtered} outdated cache entries`);
47
+ }
48
+ console.error(`[Cache] Loaded ${this.vectorStore.length} cached embeddings`);
49
+ }
50
+ } catch (error) {
51
+ console.error("[Cache] Failed to load cache:", error.message);
52
+ }
53
+ }
54
+
55
+ async save() {
56
+ if (!this.config.enableCache) return;
57
+
58
+ try {
59
+ await fs.mkdir(this.config.cacheDirectory, { recursive: true });
60
+ const cacheFile = path.join(this.config.cacheDirectory, "embeddings.json");
61
+ const hashFile = path.join(this.config.cacheDirectory, "file-hashes.json");
62
+
63
+ await Promise.all([
64
+ fs.writeFile(cacheFile, JSON.stringify(this.vectorStore, null, 2)),
65
+ fs.writeFile(hashFile, JSON.stringify(Object.fromEntries(this.fileHashes), null, 2))
66
+ ]);
67
+ } catch (error) {
68
+ console.error("[Cache] Failed to save cache:", error.message);
69
+ }
70
+ }
71
+
72
+ getVectorStore() {
73
+ return this.vectorStore;
74
+ }
75
+
76
+ setVectorStore(store) {
77
+ this.vectorStore = store;
78
+ }
79
+
80
+ getFileHash(file) {
81
+ return this.fileHashes.get(file);
82
+ }
83
+
84
+ setFileHash(file, hash) {
85
+ this.fileHashes.set(file, hash);
86
+ }
87
+
88
+ deleteFileHash(file) {
89
+ this.fileHashes.delete(file);
90
+ }
91
+
92
+ removeFileFromStore(file) {
93
+ this.vectorStore = this.vectorStore.filter(chunk => chunk.file !== file);
94
+ }
95
+
96
+
97
+ addToStore(chunk) {
98
+ this.vectorStore.push(chunk);
99
+ }
100
+
101
+ async clear() {
102
+ if (!this.config.enableCache) return;
103
+
104
+ try {
105
+ await fs.rm(this.config.cacheDirectory, { recursive: true, force: true });
106
+ this.vectorStore = [];
107
+ this.fileHashes = new Map();
108
+ console.error(`[Cache] Cache cleared successfully: ${this.config.cacheDirectory}`);
109
+ } catch (error) {
110
+ console.error("[Cache] Failed to clear cache:", error.message);
111
+ throw error;
112
+ }
113
+ }
114
+ }
package/lib/config.js ADDED
@@ -0,0 +1,146 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { ProjectDetector } from "./project-detector.js";
4
+
5
+ const DEFAULT_CONFIG = {
6
+ searchDirectory: ".",
7
+ fileExtensions: [
8
+ // JavaScript/TypeScript
9
+ "js", "ts", "jsx", "tsx", "mjs", "cjs",
10
+ // Styles
11
+ "css", "scss", "sass", "less", "styl",
12
+ // Markup
13
+ "html", "htm", "xml", "svg",
14
+ // Python
15
+ "py", "pyw", "pyx",
16
+ // Java/Kotlin/Scala
17
+ "java", "kt", "kts", "scala",
18
+ // C/C++
19
+ "c", "cpp", "cc", "cxx", "h", "hpp", "hxx",
20
+ // C#
21
+ "cs", "csx",
22
+ // Go
23
+ "go",
24
+ // Rust
25
+ "rs",
26
+ // Ruby
27
+ "rb", "rake",
28
+ // PHP
29
+ "php", "phtml",
30
+ // Swift
31
+ "swift",
32
+ // Shell scripts
33
+ "sh", "bash", "zsh", "fish",
34
+ // Config & Data
35
+ "json", "yaml", "yml", "toml", "ini", "env",
36
+ // Documentation
37
+ "md", "mdx", "txt", "rst",
38
+ // Database
39
+ "sql",
40
+ // Other
41
+ "r", "R", "lua", "vim", "pl", "pm"
42
+ ],
43
+ excludePatterns: [
44
+ "**/node_modules/**",
45
+ "**/dist/**",
46
+ "**/build/**",
47
+ "**/.git/**",
48
+ "**/coverage/**",
49
+ "**/.next/**",
50
+ "**/target/**",
51
+ "**/vendor/**"
52
+ ],
53
+ chunkSize: 15,
54
+ chunkOverlap: 3,
55
+ batchSize: 100,
56
+ maxFileSize: 1048576, // 1MB - skip files larger than this
57
+ maxResults: 5,
58
+ enableCache: true,
59
+ cacheDirectory: "./.smart-coding-cache",
60
+ watchFiles: true,
61
+ verbose: false,
62
+ embeddingModel: "Xenova/all-MiniLM-L6-v2",
63
+ semanticWeight: 0.7,
64
+ exactMatchBoost: 1.5,
65
+ smartIndexing: true
66
+ };
67
+
68
+ let config = { ...DEFAULT_CONFIG };
69
+
70
+ export async function loadConfig(workspaceDir = null) {
71
+ try {
72
+ // Determine the base directory for configuration
73
+ let baseDir;
74
+ let configPath;
75
+
76
+ if (workspaceDir) {
77
+ // Workspace mode: load config from workspace root
78
+ baseDir = path.resolve(workspaceDir);
79
+ configPath = path.join(baseDir, "config.json");
80
+ console.error(`[Config] Workspace mode: ${baseDir}`);
81
+ } else {
82
+ // Server mode: load config from server directory
83
+ const scriptDir = path.dirname(new URL(import.meta.url).pathname);
84
+ baseDir = path.resolve(scriptDir, '..');
85
+ configPath = path.join(baseDir, "config.json");
86
+ }
87
+
88
+ let userConfig = {};
89
+ try {
90
+ const configData = await fs.readFile(configPath, "utf-8");
91
+ userConfig = JSON.parse(configData);
92
+ } catch (configError) {
93
+ if (workspaceDir) {
94
+ console.error(`[Config] No config.json in workspace, using defaults`);
95
+ } else {
96
+ console.error(`[Config] No config.json found: ${configError.message}`);
97
+ }
98
+ }
99
+
100
+ config = { ...DEFAULT_CONFIG, ...userConfig };
101
+
102
+ // Set workspace-specific directories
103
+ if (workspaceDir) {
104
+ config.searchDirectory = baseDir;
105
+ config.cacheDirectory = path.join(baseDir, ".smart-coding-cache");
106
+ } else {
107
+ config.searchDirectory = path.resolve(baseDir, config.searchDirectory);
108
+ config.cacheDirectory = path.resolve(baseDir, config.cacheDirectory);
109
+ }
110
+
111
+ // Smart project detection
112
+ if (config.smartIndexing !== false) {
113
+ const detector = new ProjectDetector(config.searchDirectory);
114
+ const detectedTypes = await detector.detectProjectTypes();
115
+
116
+ if (detectedTypes.length > 0) {
117
+ const smartPatterns = detector.getSmartIgnorePatterns();
118
+
119
+ // Merge smart patterns with user patterns (user patterns take precedence)
120
+ const userPatterns = userConfig.excludePatterns || [];
121
+ config.excludePatterns = [
122
+ ...smartPatterns,
123
+ ...userPatterns
124
+ ];
125
+
126
+ console.error(`[Config] Smart indexing: ${detectedTypes.join(', ')}`);
127
+ console.error(`[Config] Applied ${smartPatterns.length} smart ignore patterns`);
128
+ } else {
129
+ console.error("[Config] No project markers detected, using default patterns");
130
+ }
131
+ }
132
+
133
+ console.error("[Config] Loaded configuration from config.json");
134
+ } catch (error) {
135
+ console.error("[Config] Using default configuration (config.json not found or invalid)");
136
+ console.error(`[Config] Error: ${error.message}`);
137
+ }
138
+
139
+ return config;
140
+ }
141
+
142
+ export function getConfig() {
143
+ return config;
144
+ }
145
+
146
+ export { DEFAULT_CONFIG };