@context-engine-bridge/context-engine-mcp-bridge 0.0.28 → 0.0.30
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/package.json +1 -1
- package/src/connectCli.js +42 -13
- package/src/mcpServer.js +164 -3
- package/src/uploader.js +65 -27
- package/.claude/settings.local.json +0 -11
- package/.sisyphus/ultrawork-state.json +0 -7
- package/docs/debugging.md +0 -20
- package/publish.sh +0 -34
package/package.json
CHANGED
package/src/connectCli.js
CHANGED
|
@@ -2,7 +2,7 @@ import process from "node:process";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { saveAuthEntry } from "./authConfig.js";
|
|
5
|
-
import { indexWorkspace } from "./uploader.js";
|
|
5
|
+
import { indexWorkspace, loadGitignore, isCodeFile } from "./uploader.js";
|
|
6
6
|
|
|
7
7
|
const SAAS_ENDPOINTS = {
|
|
8
8
|
uploadEndpoint: "https://dev.context-engine.ai/upload",
|
|
@@ -160,12 +160,35 @@ async function triggerIndexing(workspace, sessionId, authEntry) {
|
|
|
160
160
|
return result.success;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
function startWatcher(workspace,
|
|
163
|
+
function startWatcher(workspace, initialSessionId, authEntry, intervalMs) {
|
|
164
164
|
console.error(`[ctxce] Starting file watcher (sync every ${intervalMs / 1000}s)...`);
|
|
165
165
|
console.error("[ctxce] Press Ctrl+C to stop.");
|
|
166
166
|
|
|
167
167
|
let isRunning = false;
|
|
168
168
|
let pendingSync = false;
|
|
169
|
+
let sessionId = initialSessionId;
|
|
170
|
+
|
|
171
|
+
async function refreshSessionIfNeeded() {
|
|
172
|
+
// If the auth entry has an expiry and we're within 5 minutes of it,
|
|
173
|
+
// re-authenticate using the stored API key.
|
|
174
|
+
if (!authEntry || !authEntry.apiKey) return;
|
|
175
|
+
const expiresAt = authEntry.expiresAt;
|
|
176
|
+
if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0) return;
|
|
177
|
+
const nowSecs = Math.floor(Date.now() / 1000);
|
|
178
|
+
const remainingSecs = expiresAt - nowSecs;
|
|
179
|
+
if (remainingSecs > 300) return; // still valid for > 5 min
|
|
180
|
+
console.error("[ctxce] Session approaching expiry, refreshing...");
|
|
181
|
+
try {
|
|
182
|
+
const refreshed = await authenticateWithApiKey(authEntry.apiKey);
|
|
183
|
+
if (refreshed && refreshed.sessionId) {
|
|
184
|
+
sessionId = refreshed.sessionId;
|
|
185
|
+
authEntry = refreshed;
|
|
186
|
+
console.error("[ctxce] Session refreshed successfully.");
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error(`[ctxce] Session refresh failed: ${err}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
169
192
|
|
|
170
193
|
const fileHashes = new Map();
|
|
171
194
|
|
|
@@ -178,26 +201,29 @@ function startWatcher(workspace, sessionId, authEntry, intervalMs) {
|
|
|
178
201
|
}
|
|
179
202
|
}
|
|
180
203
|
|
|
204
|
+
// Use gitignore from uploader.js so the watcher ignores the same files as
|
|
205
|
+
// the bundle creator -- prevents redundant uploads for generated/ignored files.
|
|
206
|
+
const _ig = loadGitignore(workspace);
|
|
207
|
+
|
|
181
208
|
function scanDirectory(dir, files = []) {
|
|
182
209
|
try {
|
|
183
210
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
184
211
|
for (const entry of entries) {
|
|
212
|
+
if (entry.isSymbolicLink()) continue;
|
|
185
213
|
const fullPath = path.join(dir, entry.name);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
entry.name === "node_modules" ||
|
|
189
|
-
entry.name === "__pycache__" ||
|
|
190
|
-
entry.name === "venv" ||
|
|
191
|
-
entry.name === ".venv" ||
|
|
192
|
-
entry.name === "dist" ||
|
|
193
|
-
entry.name === "build") {
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
214
|
+
// Normalize to forward slashes for the `ignore` library (expects POSIX paths)
|
|
215
|
+
const relPath = path.relative(workspace, fullPath).split(path.sep).join('/');
|
|
196
216
|
|
|
217
|
+
// Use the same ignore rules as the bundle creator
|
|
218
|
+
// Directories need a trailing slash for gitignore pattern matching
|
|
197
219
|
if (entry.isDirectory()) {
|
|
220
|
+
if (_ig.ignores(relPath + '/')) continue;
|
|
198
221
|
scanDirectory(fullPath, files);
|
|
199
222
|
} else if (entry.isFile()) {
|
|
200
|
-
|
|
223
|
+
if (_ig.ignores(relPath)) continue;
|
|
224
|
+
if (isCodeFile(fullPath)) {
|
|
225
|
+
files.push(fullPath);
|
|
226
|
+
}
|
|
201
227
|
}
|
|
202
228
|
}
|
|
203
229
|
} catch {
|
|
@@ -242,6 +268,9 @@ function startWatcher(workspace, sessionId, authEntry, intervalMs) {
|
|
|
242
268
|
console.error(`[ctxce] [${now}] Syncing changes...`);
|
|
243
269
|
|
|
244
270
|
try {
|
|
271
|
+
// Refresh session before upload if approaching expiry
|
|
272
|
+
await refreshSessionIfNeeded();
|
|
273
|
+
|
|
245
274
|
const result = await indexWorkspace(
|
|
246
275
|
workspace,
|
|
247
276
|
SAAS_ENDPOINTS.uploadEndpoint,
|
package/src/mcpServer.js
CHANGED
|
@@ -3,7 +3,8 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { execSync } from "node:child_process";
|
|
6
|
-
import
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import http, { createServer } from "node:http";
|
|
7
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
10
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -14,6 +15,42 @@ import { loadAnyAuthEntry, loadAuthEntry, readConfig, saveAuthEntry } from "./au
|
|
|
14
15
|
import { maybeRemapToolArgs, maybeRemapToolResult } from "./resultPathMapping.js";
|
|
15
16
|
import * as oauthHandler from "./oauthHandler.js";
|
|
16
17
|
|
|
18
|
+
let _lspConnCache;
|
|
19
|
+
let _lspConnCacheTs = 0;
|
|
20
|
+
const LSP_CONN_CACHE_TTL = 5000;
|
|
21
|
+
const LSP_CONN_MAX_AGE = 60 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
function _isValidPort(v) {
|
|
24
|
+
const p = Number.parseInt(String(v), 10);
|
|
25
|
+
return Number.isFinite(p) && p >= 1024 && p <= 65535;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _readLspConnection() {
|
|
29
|
+
const envPort = process.env.CTXCE_LSP_PORT;
|
|
30
|
+
if (envPort && _isValidPort(envPort)) return { port: envPort, secret: process.env.CTXCE_LSP_SECRET || "" };
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
if (_lspConnCache !== undefined && (now - _lspConnCacheTs) < LSP_CONN_CACHE_TTL) return _lspConnCache;
|
|
33
|
+
|
|
34
|
+
function _cacheNull() {
|
|
35
|
+
_lspConnCache = null;
|
|
36
|
+
_lspConnCacheTs = now;
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const connPath = path.join(os.homedir(), ".ctxce", "lsp-connection.json");
|
|
42
|
+
const conn = JSON.parse(fs.readFileSync(connPath, "utf8"));
|
|
43
|
+
if (conn.port && _isValidPort(conn.port) && conn.pid) {
|
|
44
|
+
try { process.kill(conn.pid, 0); } catch (_) { return _cacheNull(); }
|
|
45
|
+
if (conn.created_at && (now - conn.created_at) > LSP_CONN_MAX_AGE) return _cacheNull();
|
|
46
|
+
_lspConnCache = { port: String(conn.port), secret: conn.secret || "" };
|
|
47
|
+
_lspConnCacheTs = now;
|
|
48
|
+
return _lspConnCache;
|
|
49
|
+
}
|
|
50
|
+
} catch (_) {}
|
|
51
|
+
return _cacheNull();
|
|
52
|
+
}
|
|
53
|
+
|
|
17
54
|
function debugLog(message) {
|
|
18
55
|
try {
|
|
19
56
|
const text = typeof message === "string" ? message : String(message);
|
|
@@ -97,6 +134,9 @@ function selectClientForTool(name, indexerClient, memoryClient) {
|
|
|
97
134
|
if (memoryClient && lowered.startsWith("memory")) {
|
|
98
135
|
return memoryClient;
|
|
99
136
|
}
|
|
137
|
+
if (lowered.startsWith("lsp_")) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
100
140
|
return indexerClient;
|
|
101
141
|
}
|
|
102
142
|
|
|
@@ -501,7 +541,13 @@ async function createBridgeServer(options) {
|
|
|
501
541
|
expiresAt > 0 &&
|
|
502
542
|
expiresAt < Math.floor(Date.now() / 1000)
|
|
503
543
|
) {
|
|
504
|
-
|
|
544
|
+
// Allow 5-minute grace period for clock skew; beyond that, reject
|
|
545
|
+
const expiredSecs = Math.floor(Date.now() / 1000) - expiresAt;
|
|
546
|
+
if (expiredSecs > 300) {
|
|
547
|
+
debugLog(`[ctxce] Session expired ${expiredSecs}s ago (beyond 5min grace), triggering re-auth.`);
|
|
548
|
+
return "";
|
|
549
|
+
}
|
|
550
|
+
debugLog("[ctxce] Session expired but within 5min grace period; using it (server will validate).");
|
|
505
551
|
return entry.sessionId;
|
|
506
552
|
}
|
|
507
553
|
return entry.sessionId;
|
|
@@ -790,6 +836,80 @@ async function createBridgeServer(options) {
|
|
|
790
836
|
const indexerTools = Array.isArray(remote?.tools) ? remote.tools.slice() : [];
|
|
791
837
|
const memoryTools = await listMemoryTools(memoryClient);
|
|
792
838
|
const tools = dedupeTools([...indexerTools, ...memoryTools]);
|
|
839
|
+
|
|
840
|
+
const lspAvailable = !!_readLspConnection();
|
|
841
|
+
if (lspAvailable) {
|
|
842
|
+
const pathProp = { type: "string", description: "Absolute file path" };
|
|
843
|
+
const lineProp = { type: "integer", description: "0-based line" };
|
|
844
|
+
const charProp = { type: "integer", description: "0-based column" };
|
|
845
|
+
const positionProps = { path: pathProp, line: lineProp, character: charProp };
|
|
846
|
+
const positionRequired = ["path", "line", "character"];
|
|
847
|
+
|
|
848
|
+
tools.push(
|
|
849
|
+
{
|
|
850
|
+
name: "lsp_diagnostics",
|
|
851
|
+
description: "Get compiler diagnostics (errors, warnings) for a file from the active language server.",
|
|
852
|
+
inputSchema: { type: "object", properties: { path: pathProp }, required: ["path"] },
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
name: "lsp_definition",
|
|
856
|
+
description: "Go to definition of symbol at position.",
|
|
857
|
+
inputSchema: { type: "object", properties: { ...positionProps }, required: positionRequired },
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
name: "lsp_references",
|
|
861
|
+
description: "Find all references to symbol at position.",
|
|
862
|
+
inputSchema: {
|
|
863
|
+
type: "object",
|
|
864
|
+
properties: { ...positionProps, includeDeclaration: { type: "boolean", description: "Include the declaration itself" } },
|
|
865
|
+
required: positionRequired,
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
name: "lsp_hover",
|
|
870
|
+
description: "Get hover information (type, docs) for symbol at position.",
|
|
871
|
+
inputSchema: { type: "object", properties: { ...positionProps }, required: positionRequired },
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
name: "lsp_document_symbols",
|
|
875
|
+
description: "Get all symbols in a document.",
|
|
876
|
+
inputSchema: { type: "object", properties: { path: pathProp }, required: ["path"] },
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
name: "lsp_workspace_symbols",
|
|
880
|
+
description: "Search for symbols across the workspace.",
|
|
881
|
+
inputSchema: { type: "object", properties: { query: { type: "string", description: "Symbol search query" } }, required: ["query"] },
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
name: "lsp_code_actions",
|
|
885
|
+
description: "Get available code actions (quick fixes, refactors) for a range.",
|
|
886
|
+
inputSchema: {
|
|
887
|
+
type: "object",
|
|
888
|
+
properties: {
|
|
889
|
+
path: pathProp,
|
|
890
|
+
startLine: { type: "integer" },
|
|
891
|
+
startChar: { type: "integer" },
|
|
892
|
+
endLine: { type: "integer" },
|
|
893
|
+
endChar: { type: "integer" },
|
|
894
|
+
},
|
|
895
|
+
required: ["path", "startLine", "startChar", "endLine", "endChar"],
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
name: "lsp_diagnostics_recent",
|
|
900
|
+
description: "Get recently collected diagnostics from file edits (errors and warnings auto-collected via language server).",
|
|
901
|
+
inputSchema: {
|
|
902
|
+
type: "object",
|
|
903
|
+
properties: {
|
|
904
|
+
paths: { type: "array", items: { type: "string" }, description: "Filter to specific file paths" },
|
|
905
|
+
severity_filter: { type: "string", enum: ["error", "warning", "info", "hint"], description: "Filter by severity" },
|
|
906
|
+
since_ms: { type: "integer", description: "Only return diagnostics newer than this Unix timestamp in ms" },
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
|
|
793
913
|
debugLog(`[ctxce] tools/list: returning ${tools.length} tools`);
|
|
794
914
|
return { tools };
|
|
795
915
|
});
|
|
@@ -840,6 +960,47 @@ async function createBridgeServer(options) {
|
|
|
840
960
|
return indexerResult;
|
|
841
961
|
}
|
|
842
962
|
|
|
963
|
+
if (name && name.toLowerCase().startsWith("lsp_")) {
|
|
964
|
+
const lspConn = _readLspConnection();
|
|
965
|
+
const lspPort = lspConn ? lspConn.port : null;
|
|
966
|
+
const lspSecret = lspConn ? lspConn.secret : "";
|
|
967
|
+
if (!lspPort) {
|
|
968
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "LSP proxy not available. Ensure VS Code extension has lsp.enabled=true." }) }] };
|
|
969
|
+
}
|
|
970
|
+
const ALLOWED_LSP_OPS = new Set(['diagnostics','definition','references','hover','document_symbols','workspace_symbols','code_actions','diagnostics_recent']);
|
|
971
|
+
const operation = name.toLowerCase().replace(/^lsp_/, "");
|
|
972
|
+
if (!ALLOWED_LSP_OPS.has(operation)) {
|
|
973
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Unknown LSP operation" }) }] };
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const lspArgs = params.arguments || {};
|
|
977
|
+
const postData = JSON.stringify(lspArgs);
|
|
978
|
+
const result = await new Promise((resolve, reject) => {
|
|
979
|
+
const req = http.request({
|
|
980
|
+
hostname: "127.0.0.1",
|
|
981
|
+
port: parseInt(lspPort, 10),
|
|
982
|
+
path: `/lsp/${operation}`,
|
|
983
|
+
method: "POST",
|
|
984
|
+
headers: Object.assign({ "Content-Type": "application/json", "Content-Length": Buffer.byteLength(postData) }, lspSecret ? { "x-lsp-handler-token": lspSecret } : {}),
|
|
985
|
+
timeout: 15000,
|
|
986
|
+
}, (res) => {
|
|
987
|
+
let data = "";
|
|
988
|
+
res.on("data", chunk => { data += chunk; });
|
|
989
|
+
res.on("end", () => {
|
|
990
|
+
try { resolve(JSON.parse(data)); } catch { resolve({ ok: false, error: "Invalid response from LSP handler" }); }
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
req.on("error", reject);
|
|
994
|
+
req.on("timeout", () => { req.destroy(); reject(new Error("LSP request timeout")); });
|
|
995
|
+
req.write(postData);
|
|
996
|
+
req.end();
|
|
997
|
+
});
|
|
998
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `LSP proxy error: ${err.message || String(err)}` }) }] };
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
843
1004
|
await initializeRemoteClients(false);
|
|
844
1005
|
|
|
845
1006
|
const timeoutMs = getBridgeToolTimeoutMs();
|
|
@@ -856,7 +1017,7 @@ async function createBridgeServer(options) {
|
|
|
856
1017
|
|
|
857
1018
|
const targetClient = selectClientForTool(name, indexerClient, memoryClient);
|
|
858
1019
|
if (!targetClient) {
|
|
859
|
-
|
|
1020
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Tool ${name} not available on any configured MCP server` }) }] };
|
|
860
1021
|
}
|
|
861
1022
|
|
|
862
1023
|
try {
|
package/src/uploader.js
CHANGED
|
@@ -36,7 +36,7 @@ const DEFAULT_IGNORES = [
|
|
|
36
36
|
|
|
37
37
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
38
38
|
|
|
39
|
-
function loadGitignore(workspacePath) {
|
|
39
|
+
export function loadGitignore(workspacePath) {
|
|
40
40
|
const ig = ignore();
|
|
41
41
|
ig.add(DEFAULT_IGNORES);
|
|
42
42
|
|
|
@@ -52,7 +52,7 @@ function loadGitignore(workspacePath) {
|
|
|
52
52
|
return ig;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function isCodeFile(filePath) {
|
|
55
|
+
export function isCodeFile(filePath) {
|
|
56
56
|
const ext = path.extname(filePath).toLowerCase();
|
|
57
57
|
if (CODE_EXTS.has(ext)) return true;
|
|
58
58
|
|
|
@@ -224,19 +224,10 @@ export async function createBundle(workspacePath, options = {}) {
|
|
|
224
224
|
export async function uploadBundle(bundlePath, manifest, uploadEndpoint, sessionId, options = {}) {
|
|
225
225
|
const { log = console.error, orgId, orgSlug } = options;
|
|
226
226
|
|
|
227
|
-
const
|
|
227
|
+
const bundleSize = fs.statSync(bundlePath).size;
|
|
228
228
|
const boundary = `----ctxce${Date.now()}${Math.random().toString(36).slice(2)}`;
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
parts.push(
|
|
233
|
-
`--${boundary}\r\n`,
|
|
234
|
-
`Content-Disposition: form-data; name="bundle"; filename="bundle.tar.gz"\r\n`,
|
|
235
|
-
`Content-Type: application/gzip\r\n\r\n`,
|
|
236
|
-
);
|
|
237
|
-
parts.push(bundleData);
|
|
238
|
-
parts.push(`\r\n`);
|
|
239
|
-
|
|
230
|
+
// Build form fields (small metadata -- kept in memory)
|
|
240
231
|
const logicalRepoId = computeLogicalRepoId(manifest.workspace_path);
|
|
241
232
|
const fields = {
|
|
242
233
|
workspace_path: manifest.workspace_path,
|
|
@@ -246,35 +237,67 @@ export async function uploadBundle(bundlePath, manifest, uploadEndpoint, session
|
|
|
246
237
|
logical_repo_id: logicalRepoId,
|
|
247
238
|
session: sessionId,
|
|
248
239
|
};
|
|
249
|
-
|
|
250
240
|
if (orgId) fields.org_id = orgId;
|
|
251
241
|
if (orgSlug) fields.org_slug = orgSlug;
|
|
252
242
|
|
|
243
|
+
// Build multipart preamble (file header) and epilogue (fields + close)
|
|
244
|
+
const filePreamble = Buffer.from(
|
|
245
|
+
`--${boundary}\r\n` +
|
|
246
|
+
`Content-Disposition: form-data; name="bundle"; filename="bundle.tar.gz"\r\n` +
|
|
247
|
+
`Content-Type: application/gzip\r\n\r\n`
|
|
248
|
+
);
|
|
249
|
+
const fileEpilogue = Buffer.from(`\r\n`);
|
|
250
|
+
|
|
251
|
+
let fieldsPart = '';
|
|
253
252
|
for (const [key, value] of Object.entries(fields)) {
|
|
254
253
|
if (value) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
`${value}\r\n`,
|
|
259
|
-
);
|
|
254
|
+
fieldsPart += `--${boundary}\r\n` +
|
|
255
|
+
`Content-Disposition: form-data; name="${key}"\r\n\r\n` +
|
|
256
|
+
`${value}\r\n`;
|
|
260
257
|
}
|
|
261
258
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
259
|
+
fieldsPart += `--${boundary}--\r\n`;
|
|
260
|
+
const fieldsBuffer = Buffer.from(fieldsPart);
|
|
261
|
+
|
|
262
|
+
// Stream the bundle file instead of loading it entirely into memory.
|
|
263
|
+
// This prevents OOM for large repositories (hundreds of MB bundles).
|
|
264
|
+
const totalLength = filePreamble.length + bundleSize + fileEpilogue.length + fieldsBuffer.length;
|
|
265
|
+
|
|
266
|
+
const { Readable } = await import('node:stream');
|
|
267
|
+
const bodyStream = new Readable({
|
|
268
|
+
read() {
|
|
269
|
+
// Push preamble
|
|
270
|
+
this.push(filePreamble);
|
|
271
|
+
// Push file data in chunks via sync read to keep it simple
|
|
272
|
+
const CHUNK = 256 * 1024; // 256KB chunks
|
|
273
|
+
const fd = fs.openSync(bundlePath, 'r');
|
|
274
|
+
try {
|
|
275
|
+
const buf = Buffer.allocUnsafe(CHUNK);
|
|
276
|
+
let bytesRead;
|
|
277
|
+
while ((bytesRead = fs.readSync(fd, buf, 0, CHUNK)) > 0) {
|
|
278
|
+
this.push(bytesRead === CHUNK ? buf : buf.subarray(0, bytesRead));
|
|
279
|
+
}
|
|
280
|
+
} finally {
|
|
281
|
+
fs.closeSync(fd);
|
|
282
|
+
}
|
|
283
|
+
// Push epilogue + fields + close
|
|
284
|
+
this.push(fileEpilogue);
|
|
285
|
+
this.push(fieldsBuffer);
|
|
286
|
+
this.push(null); // EOF
|
|
287
|
+
}
|
|
288
|
+
});
|
|
267
289
|
|
|
268
290
|
const url = `${uploadEndpoint}/api/v1/delta/upload`;
|
|
269
|
-
log(`[uploader] Uploading to ${url}...`);
|
|
291
|
+
log(`[uploader] Uploading to ${url} (${(bundleSize / 1024).toFixed(0)}KB bundle, streaming)...`);
|
|
270
292
|
|
|
271
293
|
const resp = await fetch(url, {
|
|
272
294
|
method: "POST",
|
|
273
295
|
headers: {
|
|
274
296
|
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
275
|
-
"Content-Length": String(
|
|
297
|
+
"Content-Length": String(totalLength),
|
|
276
298
|
},
|
|
277
|
-
body,
|
|
299
|
+
body: bodyStream,
|
|
300
|
+
duplex: "half", // required for streaming request bodies in Node.js fetch
|
|
278
301
|
});
|
|
279
302
|
|
|
280
303
|
let result;
|
|
@@ -298,8 +321,23 @@ export async function uploadBundle(bundlePath, manifest, uploadEndpoint, session
|
|
|
298
321
|
return { success: true, result };
|
|
299
322
|
}
|
|
300
323
|
|
|
324
|
+
let _indexInFlight = false;
|
|
301
325
|
export async function indexWorkspace(workspacePath, uploadEndpoint, sessionId, options = {}) {
|
|
302
326
|
const { log = console.error, orgId, orgSlug } = options;
|
|
327
|
+
if (_indexInFlight) {
|
|
328
|
+
log("[uploader] indexWorkspace already in progress, skipping concurrent call");
|
|
329
|
+
return { success: false, error: "already_in_progress" };
|
|
330
|
+
}
|
|
331
|
+
_indexInFlight = true;
|
|
332
|
+
try {
|
|
333
|
+
return await _indexWorkspaceInner(workspacePath, uploadEndpoint, sessionId, options);
|
|
334
|
+
} finally {
|
|
335
|
+
_indexInFlight = false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function _indexWorkspaceInner(workspacePath, uploadEndpoint, sessionId, options = {}) {
|
|
340
|
+
const { log = console.error, orgId, orgSlug } = options;
|
|
303
341
|
|
|
304
342
|
log(`[uploader] Scanning workspace: ${workspacePath}`);
|
|
305
343
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"active": true,
|
|
3
|
-
"started_at": "2026-01-25T14:25:53.146Z",
|
|
4
|
-
"original_prompt": "/oh-my-claude-sisyphus:ultrawork we need to figure out, research context engine a bit.... how to make our agent tools better via mcp... (the claude.example.md is\n clear) but maybe we need an agents.md, we have claude skills... can we make the descriptors better? research alot fo other\n tools and see",
|
|
5
|
-
"reinforcement_count": 5,
|
|
6
|
-
"last_checked_at": "2026-01-25T14:43:28.449Z"
|
|
7
|
-
}
|
package/docs/debugging.md
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"mcpServers": {
|
|
3
|
-
"context-engine": {
|
|
4
|
-
"command": "node",
|
|
5
|
-
"args": [
|
|
6
|
-
"C:/Users/Admin/Documents/GitHub/Context-Engine/ctx-mcp-bridge/bin/ctxce.js",
|
|
7
|
-
"mcp-serve",
|
|
8
|
-
"--indexer-url",
|
|
9
|
-
"http://192.168.100.249:30806/mcp",
|
|
10
|
-
"--memory-url",
|
|
11
|
-
"http://192.168.100.249:30804/mcp",
|
|
12
|
-
"--workspace",
|
|
13
|
-
"C:/Users/Admin/Documents/GitHub/Pirate Survivors"
|
|
14
|
-
],
|
|
15
|
-
"env": {
|
|
16
|
-
"CTXCE_DEBUG_LOG": "C:/Users/Admin/ctxce-mcp.log"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
package/publish.sh
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# Simple helper to login (if needed) and publish the package.
|
|
5
|
-
# Usage:
|
|
6
|
-
# ./publish.sh # publishes current version
|
|
7
|
-
# ./publish.sh 0.0.2 # bumps version to 0.0.2 then publishes
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
-
cd "$SCRIPT_DIR"
|
|
11
|
-
|
|
12
|
-
PACKAGE_NAME="@context-engine-bridge/context-engine-mcp-bridge"
|
|
13
|
-
|
|
14
|
-
echo "[publish] Verifying npm authentication..."
|
|
15
|
-
if ! npm whoami >/dev/null 2>&1; then
|
|
16
|
-
echo "[publish] Not logged in; running npm login"
|
|
17
|
-
npm login
|
|
18
|
-
else
|
|
19
|
-
echo "[publish] Already authenticated as $(npm whoami)"
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
if [[ $# -gt 0 ]]; then
|
|
23
|
-
VERSION="$1"
|
|
24
|
-
echo "[publish] Bumping version to $VERSION"
|
|
25
|
-
npm version "$VERSION" --no-git-tag-version
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
echo "[publish] Packing $PACKAGE_NAME for verification..."
|
|
29
|
-
npm pack >/dev/null
|
|
30
|
-
|
|
31
|
-
echo "[publish] Publishing $PACKAGE_NAME..."
|
|
32
|
-
npm publish --access public
|
|
33
|
-
|
|
34
|
-
echo "[publish] Done!"
|