smart-coding-mcp 1.4.1 → 2.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/README.md CHANGED
@@ -5,16 +5,18 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green.svg)](https://nodejs.org/)
7
7
 
8
- An extensible Model Context Protocol (MCP) server that provides intelligent semantic code search for AI assistants. Built with local AI models (RAG), inspired by Cursor's semantic search research.
8
+ An extensible Model Context Protocol (MCP) server that provides intelligent semantic code search for AI assistants. Built with local AI models using Matryoshka Representation Learning (MRL) for flexible embedding dimensions (64-768d), with runtime workspace switching and comprehensive status reporting.
9
9
 
10
10
  ### Available Tools
11
11
 
12
- | Tool | Description | Example |
13
- | ---------------------- | ------------------------------------------------- | ---------------------------------------------- |
14
- | `semantic_search` | Find code by meaning, not just keywords | `"Where do we validate user input?"` |
15
- | `index_codebase` | Manually trigger reindexing | Use after major refactoring or branch switches |
16
- | `clear_cache` | Reset the embeddings cache | Useful when cache becomes corrupted |
17
- | `d_check_last_version` | Get latest version of any package (20 ecosystems) | `"express"`, `"npm:react"`, `"pip:requests"` |
12
+ | Tool | Description | Example |
13
+ | ---------------------- | ------------------------------------------------- | ----------------------------------------------- |
14
+ | `semantic_search` | Find code by meaning, not just keywords | `"Where do we validate user input?"` |
15
+ | `index_codebase` | Manually trigger reindexing | Use after major refactoring or branch switches |
16
+ | `clear_cache` | Reset the embeddings cache | Useful when cache becomes corrupted |
17
+ | `d_check_last_version` | Get latest version of any package (20 ecosystems) | `"express"`, `"npm:react"`, `"pip:requests"` |
18
+ | `e_set_workspace` | Change project path at runtime | Switch to different project without restart |
19
+ | `f_get_status` | Get server info: version, index status, config | Check indexing progress, model info, cache size |
18
20
 
19
21
  ## What This Does
20
22
 
@@ -130,19 +132,22 @@ For clients that support dynamic variables (VS Code, Cursor):
130
132
 
131
133
  Override configuration settings via environment variables in your MCP config:
132
134
 
133
- | Variable | Type | Default | Description |
134
- | -------------------------------- | ------- | ------------------------- | ------------------------------------- |
135
- | `SMART_CODING_VERBOSE` | boolean | `false` | Enable detailed logging |
136
- | `SMART_CODING_BATCH_SIZE` | number | `100` | Files to process in parallel |
137
- | `SMART_CODING_MAX_FILE_SIZE` | number | `1048576` | Max file size in bytes (1MB) |
138
- | `SMART_CODING_CHUNK_SIZE` | number | `25` | Lines of code per chunk |
139
- | `SMART_CODING_MAX_RESULTS` | number | `5` | Max search results |
140
- | `SMART_CODING_SMART_INDEXING` | boolean | `true` | Enable smart project detection |
141
- | `SMART_CODING_WATCH_FILES` | boolean | `false` | Enable file watching for auto-reindex |
142
- | `SMART_CODING_SEMANTIC_WEIGHT` | number | `0.7` | Weight for semantic similarity (0-1) |
143
- | `SMART_CODING_EXACT_MATCH_BOOST` | number | `1.5` | Boost for exact text matches |
144
- | `SMART_CODING_EMBEDDING_MODEL` | string | `Xenova/all-MiniLM-L6-v2` | AI embedding model to use |
145
- | `SMART_CODING_WORKER_THREADS` | string | `auto` | Worker threads (`auto` or 1-32) |
135
+ | Variable | Type | Default | Description |
136
+ | ---------------------------------- | ------- | -------------------------------- | ------------------------------------------ |
137
+ | `SMART_CODING_VERBOSE` | boolean | `false` | Enable detailed logging |
138
+ | `SMART_CODING_BATCH_SIZE` | number | `100` | Files to process in parallel |
139
+ | `SMART_CODING_MAX_FILE_SIZE` | number | `1048576` | Max file size in bytes (1MB) |
140
+ | `SMART_CODING_CHUNK_SIZE` | number | `25` | Lines of code per chunk |
141
+ | `SMART_CODING_MAX_RESULTS` | number | `5` | Max search results |
142
+ | `SMART_CODING_SMART_INDEXING` | boolean | `true` | Enable smart project detection |
143
+ | `SMART_CODING_WATCH_FILES` | boolean | `false` | Enable file watching for auto-reindex |
144
+ | `SMART_CODING_SEMANTIC_WEIGHT` | number | `0.7` | Weight for semantic similarity (0-1) |
145
+ | `SMART_CODING_EXACT_MATCH_BOOST` | number | `1.5` | Boost for exact text matches |
146
+ | `SMART_CODING_EMBEDDING_MODEL` | string | `nomic-ai/nomic-embed-text-v1.5` | AI embedding model to use |
147
+ | `SMART_CODING_EMBEDDING_DIMENSION` | number | `256` | MRL dimension (64, 128, 256, 512, 768) |
148
+ | `SMART_CODING_DEVICE` | string | `cpu` | Inference device (`cpu`, `webgpu`, `auto`) |
149
+ | `SMART_CODING_CHUNKING_MODE` | string | `smart` | Code chunking (`smart`, `ast`, `line`) |
150
+ | `SMART_CODING_WORKER_THREADS` | string | `auto` | Worker threads (`auto` or 1-32) |
146
151
 
147
152
  **Example with environment variables:**
148
153
 
@@ -166,16 +171,72 @@ Override configuration settings via environment variables in your MCP config:
166
171
 
167
172
  ## How It Works
168
173
 
169
- The server indexes your code in four steps:
174
+ ```mermaid
175
+ flowchart TB
176
+ subgraph IDE["IDE / AI Assistant"]
177
+ Agent["AI Agent<br/>(Claude, GPT, Gemini)"]
178
+ end
179
+
180
+ subgraph MCP["Smart Coding MCP Server"]
181
+ direction TB
182
+ Protocol["Model Context Protocol<br/>JSON-RPC over stdio"]
183
+ Tools["MCP Tools<br/>semantic_search | index_codebase | set_workspace | get_status"]
184
+
185
+ subgraph Indexing["Indexing Pipeline"]
186
+ Discovery["File Discovery<br/>glob patterns + smart ignore"]
187
+ Chunking["Code Chunking<br/>Smart (regex) / AST (Tree-sitter)"]
188
+ Embedding["AI Embedding<br/>transformers.js + ONNX Runtime"]
189
+ end
190
+
191
+ subgraph AI["AI Model"]
192
+ Model["nomic-embed-text-v1.5<br/>Matryoshka Representation Learning"]
193
+ Dimensions["Flexible Dimensions<br/>64 | 128 | 256 | 512 | 768"]
194
+ Normalize["Layer Norm → Slice → L2 Normalize"]
195
+ end
196
+
197
+ subgraph Search["Search"]
198
+ QueryEmbed["Query → Vector"]
199
+ Cosine["Cosine Similarity"]
200
+ Hybrid["Hybrid Search<br/>Semantic + Exact Match Boost"]
201
+ end
202
+ end
203
+
204
+ subgraph Storage["Cache"]
205
+ Vectors["Vector Store<br/>embeddings.json"]
206
+ Hashes["File Hashes<br/>Incremental updates"]
207
+ end
208
+
209
+ Agent <-->|"MCP Protocol"| Protocol
210
+ Protocol --> Tools
211
+
212
+ Tools --> Discovery
213
+ Discovery --> Chunking
214
+ Chunking --> Embedding
215
+ Embedding --> Model
216
+ Model --> Dimensions
217
+ Dimensions --> Normalize
218
+ Normalize --> Vectors
219
+
220
+ Tools --> QueryEmbed
221
+ QueryEmbed --> Model
222
+ Cosine --> Hybrid
223
+ Vectors --> Cosine
224
+ Hybrid --> Agent
225
+ ```
226
+
227
+ ### Tech Stack
170
228
 
171
- 1. **Discovery**: Scans your project for source files
172
- 2. **Chunking**: Breaks code into meaningful pieces (respecting function boundaries)
173
- 3. **Embedding**: Converts each chunk to a vector using a local AI model
174
- 4. **Storage**: Saves embeddings to `.smart-coding-cache/` for fast startup
229
+ | Component | Technology |
230
+ | ------------- | ------------------------------------- |
231
+ | **Protocol** | Model Context Protocol (JSON-RPC) |
232
+ | **AI Model** | nomic-embed-text-v1.5 (MRL) |
233
+ | **Inference** | transformers.js + ONNX Runtime |
234
+ | **Chunking** | Smart regex / Tree-sitter AST |
235
+ | **Search** | Cosine similarity + exact match boost |
175
236
 
176
- When you search, your query is converted to the same vector format and compared against all code chunks using cosine similarity. The most relevant matches are returned.
237
+ ### Search Flow
177
238
 
178
- ![How It Works](how-its-works.png)
239
+ Query Vector embedding → Cosine similarity → Ranked results
179
240
 
180
241
  ## Examples
181
242
 
@@ -214,11 +275,18 @@ Finds all try/catch blocks and error handling patterns.
214
275
 
215
276
  ## Technical Details
216
277
 
217
- **Embedding Model**: all-MiniLM-L6-v2 via transformers.js
278
+ **Embedding Model**: nomic-embed-text-v1.5 via transformers.js v3
279
+
280
+ - Matryoshka Representation Learning (MRL) for flexible dimensions
281
+ - Configurable output: 64, 128, 256, 512, or 768 dimensions
282
+ - Longer context (8192 tokens vs 256 for MiniLM)
283
+ - Better code understanding through specialized training
284
+ - WebGPU support for up to 100x faster inference (when available)
285
+
286
+ **Legacy Model**: all-MiniLM-L6-v2 (fallback)
218
287
 
219
- - Fast inference (CPU-friendly)
220
- - Small model size (~100MB)
221
- - Good accuracy for code search
288
+ - Fast inference, small footprint (~100MB)
289
+ - Fixed 384-dimensional output
222
290
 
223
291
  **Vector Similarity**: Cosine similarity
224
292
 
package/config.json CHANGED
@@ -60,7 +60,10 @@
60
60
  "cacheDirectory": "./.smart-coding-cache",
61
61
  "watchFiles": false,
62
62
  "verbose": false,
63
- "embeddingModel": "Xenova/all-MiniLM-L6-v2",
63
+ "embeddingModel": "nomic-ai/nomic-embed-text-v1.5",
64
+ "embeddingDimension": 256,
65
+ "device": "auto",
66
+ "chunkingMode": "smart",
64
67
  "semanticWeight": 0.7,
65
68
  "exactMatchBoost": 1.5,
66
69
  "workerThreads": "auto"
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Get Status Feature
3
+ *
4
+ * MCP tool to return comprehensive status information about the server.
5
+ * Useful for agents to understand current state and configuration.
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { createRequire } from 'module';
11
+
12
+ const require = createRequire(import.meta.url);
13
+ const packageJson = require('../package.json');
14
+
15
+ /**
16
+ * Get tool definition for MCP registration
17
+ */
18
+ export function getToolDefinition(config) {
19
+ return {
20
+ name: "f_get_status",
21
+ description: "Get comprehensive status information about the Smart Coding MCP server. Returns version, workspace path, model configuration, indexing status, and cache information. Useful for understanding the current state of the semantic search system.",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {},
25
+ required: []
26
+ }
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Status Reporter class
32
+ */
33
+ export class StatusReporter {
34
+ constructor(config, cache, indexer, embedder) {
35
+ this.config = config;
36
+ this.cache = cache;
37
+ this.indexer = indexer;
38
+ this.embedder = embedder;
39
+ this.startTime = Date.now();
40
+ }
41
+
42
+ /**
43
+ * Get comprehensive status
44
+ */
45
+ async getStatus() {
46
+ const vectorStore = this.cache?.getVectorStore() || [];
47
+
48
+ // Get unique files from vector store
49
+ const uniqueFiles = new Set(vectorStore.map(v => v.file));
50
+
51
+ // Get cache size
52
+ let cacheSizeBytes = 0;
53
+ try {
54
+ const cachePath = path.join(this.config.cacheDirectory, 'embeddings.json');
55
+ const stats = await fs.stat(cachePath);
56
+ cacheSizeBytes = stats.size;
57
+ } catch {
58
+ // Cache file doesn't exist yet
59
+ }
60
+
61
+ // Determine index status
62
+ let indexStatus = 'empty';
63
+ if (this.indexer?.isIndexing) {
64
+ indexStatus = 'indexing';
65
+ } else if (vectorStore.length > 0) {
66
+ indexStatus = 'ready';
67
+ }
68
+
69
+ return {
70
+ version: packageJson.version,
71
+ uptime: Math.floor((Date.now() - this.startTime) / 1000),
72
+
73
+ workspace: {
74
+ path: this.config.searchDirectory,
75
+ cacheDirectory: this.config.cacheDirectory
76
+ },
77
+
78
+ model: {
79
+ name: this.embedder?.modelName || this.config.embeddingModel,
80
+ dimension: this.embedder?.dimension || this.config.embeddingDimension,
81
+ device: this.embedder?.device || this.config.device
82
+ },
83
+
84
+ index: {
85
+ status: indexStatus,
86
+ filesIndexed: uniqueFiles.size,
87
+ chunksCount: vectorStore.length,
88
+ chunkingMode: this.config.chunkingMode
89
+ },
90
+
91
+ cache: {
92
+ enabled: this.config.enableCache,
93
+ path: this.config.cacheDirectory,
94
+ sizeBytes: cacheSizeBytes,
95
+ sizeFormatted: formatBytes(cacheSizeBytes)
96
+ },
97
+
98
+ config: {
99
+ maxResults: this.config.maxResults,
100
+ chunkSize: this.config.chunkSize,
101
+ semanticWeight: this.config.semanticWeight,
102
+ exactMatchBoost: this.config.exactMatchBoost,
103
+ workerThreads: this.config.workerThreads
104
+ }
105
+ };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Format bytes to human readable
111
+ */
112
+ function formatBytes(bytes) {
113
+ if (bytes === 0) return '0 B';
114
+ const k = 1024;
115
+ const sizes = ['B', 'KB', 'MB', 'GB'];
116
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
117
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
118
+ }
119
+
120
+ /**
121
+ * Handle MCP tool call
122
+ */
123
+ export async function handleToolCall(request, instance) {
124
+ const status = await instance.getStatus();
125
+
126
+ return {
127
+ content: [{
128
+ type: "text",
129
+ text: JSON.stringify(status, null, 2)
130
+ }]
131
+ };
132
+ }
@@ -25,6 +25,13 @@ export class CodebaseIndexer {
25
25
  * Initialize worker thread pool for parallel embedding
26
26
  */
27
27
  async initializeWorkers() {
28
+ // Force single-threaded mode for nomic models (transformers.js v3 worker thread issue)
29
+ const isNomicModel = this.config.embeddingModel?.includes('nomic');
30
+ if (isNomicModel) {
31
+ console.error("[Indexer] Single-threaded mode (nomic model - workers disabled for stability)");
32
+ return;
33
+ }
34
+
28
35
  const numWorkers = this.config.workerThreads === "auto"
29
36
  ? Math.max(1, os.cpus().length - 1)
30
37
  : (this.config.workerThreads || 1);
@@ -48,6 +55,7 @@ export class CodebaseIndexer {
48
55
  const worker = new Worker(workerPath, {
49
56
  workerData: {
50
57
  embeddingModel: this.config.embeddingModel,
58
+ embeddingDimension: this.config.embeddingDimension,
51
59
  verbose: this.config.verbose
52
60
  }
53
61
  });
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Set Workspace Feature
3
+ *
4
+ * MCP tool to change the project workspace path at runtime.
5
+ * Useful when agent detects it's in a different directory.
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+
11
+ /**
12
+ * Get tool definition for MCP registration
13
+ */
14
+ export function getToolDefinition(config) {
15
+ return {
16
+ name: "e_set_workspace",
17
+ description: "Change the project workspace path at runtime. Use this when you detect the current workspace is incorrect or you need to switch to a different project directory. Creates cache folder automatically and optionally re-indexes the new workspace.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ path: {
22
+ type: "string",
23
+ description: "Absolute path to the new workspace directory"
24
+ },
25
+ clearCache: {
26
+ type: "boolean",
27
+ description: "Whether to clear existing cache before switching (default: false)"
28
+ },
29
+ reindex: {
30
+ type: "boolean",
31
+ description: "Whether to trigger re-indexing after switching (default: true)"
32
+ }
33
+ },
34
+ required: ["path"]
35
+ }
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Workspace Manager class
41
+ */
42
+ export class WorkspaceManager {
43
+ constructor(config, cache, indexer) {
44
+ this.config = config;
45
+ this.cache = cache;
46
+ this.indexer = indexer;
47
+ }
48
+
49
+ /**
50
+ * Set new workspace path
51
+ */
52
+ async setWorkspace(newPath, options = {}) {
53
+ const { clearCache = false, reindex = true } = options;
54
+
55
+ // Validate path
56
+ try {
57
+ const stats = await fs.stat(newPath);
58
+ if (!stats.isDirectory()) {
59
+ return {
60
+ success: false,
61
+ error: `Path is not a directory: ${newPath}`
62
+ };
63
+ }
64
+ } catch (err) {
65
+ return {
66
+ success: false,
67
+ error: `Path does not exist: ${newPath}`
68
+ };
69
+ }
70
+
71
+ const oldPath = this.config.searchDirectory;
72
+
73
+ // Update config
74
+ this.config.searchDirectory = newPath;
75
+
76
+ // Update cache directory
77
+ const newCacheDir = path.join(newPath, '.smart-coding-cache');
78
+ this.config.cacheDirectory = newCacheDir;
79
+
80
+ // Ensure cache directory exists
81
+ try {
82
+ await fs.mkdir(newCacheDir, { recursive: true });
83
+ } catch (err) {
84
+ // Ignore if already exists
85
+ }
86
+
87
+ // Clear cache if requested
88
+ if (clearCache && this.cache) {
89
+ this.cache.setVectorStore([]);
90
+ this.cache.fileHashes = new Map();
91
+ console.error(`[Workspace] Cache cleared for new workspace`);
92
+ }
93
+
94
+ // Update cache path and reload
95
+ if (this.cache) {
96
+ this.cache.config = this.config;
97
+ await this.cache.load();
98
+ }
99
+
100
+ // Update indexer config
101
+ if (this.indexer) {
102
+ this.indexer.config = this.config;
103
+ }
104
+
105
+ console.error(`[Workspace] Changed from ${oldPath} to ${newPath}`);
106
+
107
+ // Trigger re-indexing if requested
108
+ let indexResult = null;
109
+ if (reindex && this.indexer) {
110
+ console.error(`[Workspace] Starting re-indexing...`);
111
+ try {
112
+ indexResult = await this.indexer.indexAll(clearCache);
113
+ } catch (err) {
114
+ console.error(`[Workspace] Re-indexing error: ${err.message}`);
115
+ }
116
+ }
117
+
118
+ return {
119
+ success: true,
120
+ oldPath,
121
+ newPath,
122
+ cacheDirectory: newCacheDir,
123
+ reindexed: reindex,
124
+ indexResult
125
+ };
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Handle MCP tool call
131
+ */
132
+ export async function handleToolCall(request, instance) {
133
+ const { path: newPath, clearCache, reindex } = request.params.arguments || {};
134
+
135
+ if (!newPath) {
136
+ return {
137
+ content: [{
138
+ type: "text",
139
+ text: JSON.stringify({
140
+ success: false,
141
+ error: "Missing required parameter: path"
142
+ }, null, 2)
143
+ }]
144
+ };
145
+ }
146
+
147
+ const result = await instance.setWorkspace(newPath, { clearCache, reindex });
148
+
149
+ return {
150
+ content: [{
151
+ type: "text",
152
+ text: JSON.stringify(result, null, 2)
153
+ }]
154
+ };
155
+ }
package/index.js CHANGED
@@ -2,7 +2,6 @@
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
- import { pipeline } from "@xenova/transformers";
6
5
  import fs from "fs/promises";
7
6
  import { createRequire } from "module";
8
7
 
@@ -12,6 +11,7 @@ const packageJson = require("./package.json");
12
11
 
13
12
  import { loadConfig } from "./lib/config.js";
14
13
  import { EmbeddingsCache } from "./lib/cache.js";
14
+ import { createEmbedder } from "./lib/mrl-embedder.js";
15
15
  import { CodebaseIndexer } from "./features/index-codebase.js";
16
16
  import { HybridSearch } from "./features/hybrid-search.js";
17
17
 
@@ -19,6 +19,8 @@ import * as IndexCodebaseFeature from "./features/index-codebase.js";
19
19
  import * as HybridSearchFeature from "./features/hybrid-search.js";
20
20
  import * as ClearCacheFeature from "./features/clear-cache.js";
21
21
  import * as CheckLastVersionFeature from "./features/check-last-version.js";
22
+ import * as SetWorkspaceFeature from "./features/set-workspace.js";
23
+ import * as GetStatusFeature from "./features/get-status.js";
22
24
 
23
25
  // Parse workspace from command line arguments
24
26
  const args = process.argv.slice(2);
@@ -78,6 +80,16 @@ const features = [
78
80
  module: CheckLastVersionFeature,
79
81
  instance: null,
80
82
  handler: CheckLastVersionFeature.handleToolCall
83
+ },
84
+ {
85
+ module: SetWorkspaceFeature,
86
+ instance: null,
87
+ handler: SetWorkspaceFeature.handleToolCall
88
+ },
89
+ {
90
+ module: GetStatusFeature,
91
+ instance: null,
92
+ handler: GetStatusFeature.handleToolCall
81
93
  }
82
94
  ];
83
95
 
@@ -94,9 +106,10 @@ async function initialize() {
94
106
  process.exit(1);
95
107
  }
96
108
 
97
- // Load AI model
109
+ // Load AI model using MRL embedder factory
98
110
  console.error("[Server] Loading AI embedding model (this may take time on first run)...");
99
- embedder = await pipeline("feature-extraction", config.embeddingModel);
111
+ embedder = await createEmbedder(config);
112
+ console.error(`[Server] Model: ${embedder.modelName} (${embedder.dimension}d, device: ${embedder.device})`);
100
113
 
101
114
  // Initialize cache
102
115
  cache = new EmbeddingsCache(config);
@@ -113,6 +126,12 @@ async function initialize() {
113
126
  features[1].instance = indexer;
114
127
  features[2].instance = cacheClearer;
115
128
  features[3].instance = versionChecker;
129
+
130
+ // Initialize new tools
131
+ const workspaceManager = new SetWorkspaceFeature.WorkspaceManager(config, cache, indexer);
132
+ const statusReporter = new GetStatusFeature.StatusReporter(config, cache, indexer, embedder);
133
+ features[4].instance = workspaceManager;
134
+ features[5].instance = statusReporter;
116
135
 
117
136
  // Start indexing in background (non-blocking)
118
137
  console.error("[Server] Starting background indexing...");