@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "Context Engine MCP bridge (http/stdio proxy combining indexer + memory servers)",
5
5
  "bin": {
6
6
  "ctxce": "bin/ctxce.js",
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, sessionId, authEntry, intervalMs) {
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
- if (entry.name.startsWith(".") ||
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
- files.push(fullPath);
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 { createServer } from "node:http";
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
- debugLog("[ctxce] Stored auth session has local expiry in the past; attempting to use it anyway (server will validate).");
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
- throw new Error(`Tool ${name} not available on any configured MCP server`);
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 bundleData = fs.readFileSync(bundlePath);
227
+ const bundleSize = fs.statSync(bundlePath).size;
228
228
  const boundary = `----ctxce${Date.now()}${Math.random().toString(36).slice(2)}`;
229
229
 
230
- const parts = [];
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
- parts.push(
256
- `--${boundary}\r\n`,
257
- `Content-Disposition: form-data; name="${key}"\r\n\r\n`,
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
- parts.push(`--${boundary}--\r\n`);
264
-
265
- const bodyParts = parts.map(p => typeof p === "string" ? Buffer.from(p) : p);
266
- const body = Buffer.concat(bodyParts);
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(body.length),
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,11 +0,0 @@
1
- {
2
- "enableAllProjectMcpServers": true,
3
- "enabledMcpjsonServers": [
4
- "context-engine"
5
- ],
6
- "permissions": {
7
- "allow": [
8
- "mcp__context-engine__repo_search"
9
- ]
10
- }
11
- }
@@ -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!"