@minhpnq1807/contextos 0.5.42 → 0.5.45

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.
@@ -0,0 +1,116 @@
1
+ import { parse } from "smol-toml";
2
+
3
+ export function readMcpServersFromToml(content) {
4
+ const config = parseToml(content);
5
+ const servers = config.mcp_servers && typeof config.mcp_servers === "object"
6
+ ? config.mcp_servers
7
+ : {};
8
+
9
+ return Object.entries(servers)
10
+ .filter(([, server]) => server && typeof server.command === "string")
11
+ .map(([name, server]) => ({
12
+ name,
13
+ command: server.command,
14
+ args: Array.isArray(server.args) ? server.args.map(String) : []
15
+ }));
16
+ }
17
+
18
+ export function updateMcpServerFields(content, name, fields) {
19
+ parseToml(content);
20
+ const lines = String(content || "").split(/\r?\n/);
21
+ const section = findMcpServerSection(lines, name);
22
+ if (!section) return content;
23
+
24
+ let body = lines.slice(section.start + 1, section.end);
25
+ for (const [key, value] of Object.entries(fields)) {
26
+ body = replaceOrInsertField(body, key, formatTomlValue(value));
27
+ }
28
+ lines.splice(section.start + 1, section.end - section.start - 1, ...body);
29
+ return lines.join("\n");
30
+ }
31
+
32
+ export function formatTomlValue(value) {
33
+ if (Array.isArray(value)) return `[${value.map((item) => JSON.stringify(String(item))).join(", ")}]`;
34
+ return JSON.stringify(String(value));
35
+ }
36
+
37
+ function parseToml(content) {
38
+ return parse(String(content || ""));
39
+ }
40
+
41
+ function findMcpServerSection(lines, name) {
42
+ for (let index = 0; index < lines.length; index += 1) {
43
+ const sectionName = mcpServerNameFromHeader(lines[index]);
44
+ if (sectionName !== name) continue;
45
+ let end = lines.length;
46
+ for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
47
+ if (/^\s*\[/.test(lines[cursor])) {
48
+ end = cursor;
49
+ break;
50
+ }
51
+ }
52
+ return { start: index, end };
53
+ }
54
+ return null;
55
+ }
56
+
57
+ function mcpServerNameFromHeader(line) {
58
+ const match = String(line || "").match(/^\s*\[mcp_servers\.([^\]]+)\]\s*(?:#.*)?$/);
59
+ if (!match || match[1].includes(".tools.")) return null;
60
+ const raw = match[1].trim();
61
+ if (!raw.startsWith('"')) return raw;
62
+ try {
63
+ return JSON.parse(raw);
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ function replaceOrInsertField(body, key, value) {
70
+ const next = [...body];
71
+ const start = next.findIndex((line) => new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(line));
72
+ const line = `${key} = ${value}`;
73
+ if (start < 0) {
74
+ next.unshift(line);
75
+ return next;
76
+ }
77
+
78
+ let end = start + 1;
79
+ if (valueForField(next[start]).trimStart().startsWith("[")) {
80
+ while (end < next.length && !isBalancedTomlArray(next.slice(start, end).join("\n"))) end += 1;
81
+ }
82
+ next.splice(start, Math.max(1, end - start), line);
83
+ return next;
84
+ }
85
+
86
+ function valueForField(line) {
87
+ return String(line || "").slice(String(line || "").indexOf("=") + 1);
88
+ }
89
+
90
+ function isBalancedTomlArray(value) {
91
+ let depth = 0;
92
+ let quoted = false;
93
+ let escaped = false;
94
+ for (const char of String(value || "")) {
95
+ if (escaped) {
96
+ escaped = false;
97
+ continue;
98
+ }
99
+ if (char === "\\" && quoted) {
100
+ escaped = true;
101
+ continue;
102
+ }
103
+ if (char === '"') {
104
+ quoted = !quoted;
105
+ continue;
106
+ }
107
+ if (quoted) continue;
108
+ if (char === "[") depth += 1;
109
+ if (char === "]") depth -= 1;
110
+ }
111
+ return depth <= 0;
112
+ }
113
+
114
+ function escapeRegExp(value) {
115
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
116
+ }
@@ -6,7 +6,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6
6
 
7
7
  import { isModelCacheReady, modelCacheDir } from "../lib/embedding-scorer.js";
8
8
  import { scoreContext } from "../lib/score-context.js";
9
- import { ctxMcpSocketPath } from "../lib/ctx-mcp-client.js";
9
+ import { CTX_MCP_BRIDGE_REVISION, ctxMcpSocketPath } from "../lib/ctx-mcp-client.js";
10
10
  import { defaultDataRoot } from "../lib/workspace-data.js";
11
11
  import { createContextOSMcpServer } from "./contextos-server.js";
12
12
 
@@ -33,6 +33,9 @@ function startBridge() {
33
33
  fs.rmSync(socketPath, { force: true });
34
34
  const bridge = net.createServer((socket) => {
35
35
  let raw = "";
36
+ socket.on("error", () => {
37
+ // Clients may time out and close while scoring is still in progress.
38
+ });
36
39
  socket.on("data", (chunk) => {
37
40
  raw += chunk.toString("utf8");
38
41
  if (raw.includes("\n")) handleBridgeRequest(socket, raw);
@@ -68,9 +71,10 @@ async function handleBridgeRequest(socket, raw) {
68
71
  skills: payload.skills,
69
72
  workflows: payload.workflows
70
73
  });
71
- socket.end(JSON.stringify(result));
74
+ socket.end(JSON.stringify({ ...result, bridgeRevision: CTX_MCP_BRIDGE_REVISION }));
72
75
  } catch (error) {
73
76
  socket.end(JSON.stringify({
77
+ bridgeRevision: CTX_MCP_BRIDGE_REVISION,
74
78
  error: error?.message || String(error),
75
79
  scoredRules: [],
76
80
  suggestedFiles: [],