forked from omar-haris/smart-coding-mcp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
executable file
·226 lines (192 loc) · 6.63 KB
/
index.js
File metadata and controls
executable file
·226 lines (192 loc) · 6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import fs from "fs/promises";
import { createRequire } from "module";
// Import package.json for version
const require = createRequire(import.meta.url);
const packageJson = require("./package.json");
import { loadConfig } from "./lib/config.js";
import { EmbeddingsCache } from "./lib/cache.js";
import { createEmbedder } from "./lib/mrl-embedder.js";
import { CodebaseIndexer } from "./features/index-codebase.js";
import { HybridSearch } from "./features/hybrid-search.js";
import * as IndexCodebaseFeature from "./features/index-codebase.js";
import * as HybridSearchFeature from "./features/hybrid-search.js";
import * as ClearCacheFeature from "./features/clear-cache.js";
import * as CheckLastVersionFeature from "./features/check-last-version.js";
import * as SetWorkspaceFeature from "./features/set-workspace.js";
import * as GetStatusFeature from "./features/get-status.js";
// Parse workspace from command line arguments
const args = process.argv.slice(2);
const workspaceIndex = args.findIndex(arg => arg.startsWith('--workspace'));
let workspaceDir = process.cwd(); // Default to current directory
if (workspaceIndex !== -1) {
const arg = args[workspaceIndex];
let rawWorkspace = null;
if (arg.includes('=')) {
rawWorkspace = arg.split('=')[1];
} else if (workspaceIndex + 1 < args.length) {
rawWorkspace = args[workspaceIndex + 1];
}
// Check if IDE variable wasn't expanded (contains ${})
if (rawWorkspace && rawWorkspace.includes('${')) {
console.error(`[Server] FATAL: Workspace variable "${rawWorkspace}" was not expanded by your IDE.`);
console.error(`[Server] This typically means your MCP client does not support dynamic variables.`);
console.error(`[Server] Please use an absolute path instead: --workspace /path/to/your/project`);
process.exit(1);
} else if (rawWorkspace) {
workspaceDir = rawWorkspace;
}
if (workspaceDir) {
console.error(`[Server] Workspace mode: ${workspaceDir}`);
}
}
// Global state
let embedder = null;
let cache = null;
let indexer = null;
let hybridSearch = null;
let config = null;
// Feature registry - ordered by priority (semantic_search first as primary tool)
const features = [
{
module: HybridSearchFeature,
instance: null,
handler: HybridSearchFeature.handleToolCall
},
{
module: IndexCodebaseFeature,
instance: null,
handler: IndexCodebaseFeature.handleToolCall
},
{
module: ClearCacheFeature,
instance: null,
handler: ClearCacheFeature.handleToolCall
},
{
module: CheckLastVersionFeature,
instance: null,
handler: CheckLastVersionFeature.handleToolCall
},
{
module: SetWorkspaceFeature,
instance: null,
handler: SetWorkspaceFeature.handleToolCall
},
{
module: GetStatusFeature,
instance: null,
handler: GetStatusFeature.handleToolCall
}
];
// Initialize application
async function initialize() {
// Load configuration with workspace support
config = await loadConfig(workspaceDir);
// Ensure search directory exists
try {
await fs.access(config.searchDirectory);
} catch {
console.error(`[Server] Error: Search directory "${config.searchDirectory}" does not exist`);
process.exit(1);
}
// Load AI model using MRL embedder factory
console.error("[Server] Loading AI embedding model (this may take time on first run)...");
embedder = await createEmbedder(config);
console.error(`[Server] Model: ${embedder.modelName} (${embedder.dimension}d, device: ${embedder.device})`);
// Initialize cache
cache = new EmbeddingsCache(config);
await cache.load();
// Initialize features
indexer = new CodebaseIndexer(embedder, cache, config, server);
hybridSearch = new HybridSearch(embedder, cache, config);
const cacheClearer = new ClearCacheFeature.CacheClearer(embedder, cache, config, indexer);
const versionChecker = new CheckLastVersionFeature.VersionChecker(config);
// Store feature instances (matches features array order)
features[0].instance = hybridSearch;
features[1].instance = indexer;
features[2].instance = cacheClearer;
features[3].instance = versionChecker;
// Initialize new tools
const workspaceManager = new SetWorkspaceFeature.WorkspaceManager(config, cache, indexer);
const statusReporter = new GetStatusFeature.StatusReporter(config, cache, indexer, embedder);
features[4].instance = workspaceManager;
features[5].instance = statusReporter;
// Start indexing in background (non-blocking)
console.error("[Server] Starting background indexing...");
indexer.indexAll().then(() => {
// Only start file watcher if explicitly enabled in config
if (config.watchFiles) {
indexer.setupFileWatcher();
}
}).catch(err => {
console.error("[Server] Background indexing error:", err.message);
});
}
// Setup MCP server
const server = new Server(
{
name: "smart-coding-mcp",
version: packageJson.version
},
{
capabilities: {
tools: {}
}
}
);
// Register tools from all features
server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = [];
for (const feature of features) {
const toolDef = feature.module.getToolDefinition(config);
tools.push(toolDef);
}
return { tools };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
for (const feature of features) {
const toolDef = feature.module.getToolDefinition(config);
if (request.params.name === toolDef.name) {
return await feature.handler(request, feature.instance);
}
}
return {
content: [{
type: "text",
text: `Unknown tool: ${request.params.name}`
}]
};
});
// Main entry point
async function main() {
await initialize();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("[Server] Smart Coding MCP server ready!");
}
// Graceful shutdown
process.on('SIGINT', async () => {
console.error("\n[Server] Shutting down gracefully...");
// Stop file watcher
if (indexer && indexer.watcher) {
await indexer.watcher.close();
console.error("[Server] File watcher stopped");
}
// Save cache
if (cache) {
await cache.save();
console.error("[Server] Cache saved");
}
console.error("[Server] Goodbye!");
process.exit(0);
});
process.on('SIGTERM', async () => {
console.error("\n[Server] Received SIGTERM, shutting down...");
process.exit(0);
});
main().catch(console.error);