opencontext-mcp 1.2.1 → 1.3.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.
Files changed (73) hide show
  1. package/README.md +38 -12
  2. package/dist/cli/index.d.ts +25 -0
  3. package/dist/cli/index.js +97 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/{config.d.ts → config/index.d.ts} +1 -1
  6. package/dist/config/index.js +62 -0
  7. package/dist/config/index.js.map +1 -0
  8. package/dist/index.js +52 -9
  9. package/dist/index.js.map +1 -1
  10. package/dist/init/constants.d.ts +6 -0
  11. package/dist/init/constants.js +17 -0
  12. package/dist/init/constants.js.map +1 -0
  13. package/dist/init/display.d.ts +9 -0
  14. package/dist/init/display.js +28 -0
  15. package/dist/init/display.js.map +1 -0
  16. package/dist/init/files.d.ts +12 -0
  17. package/dist/init/files.js +39 -0
  18. package/dist/init/files.js.map +1 -0
  19. package/dist/init/index.d.ts +6 -0
  20. package/dist/init/index.js +157 -0
  21. package/dist/init/index.js.map +1 -0
  22. package/dist/init/inspect.d.ts +21 -0
  23. package/dist/init/inspect.js +57 -0
  24. package/dist/init/inspect.js.map +1 -0
  25. package/dist/init/integrations/claude.d.ts +17 -0
  26. package/dist/init/integrations/claude.js +94 -0
  27. package/dist/init/integrations/claude.js.map +1 -0
  28. package/dist/init/integrations/opencode.d.ts +20 -0
  29. package/dist/init/integrations/opencode.js +108 -0
  30. package/dist/init/integrations/opencode.js.map +1 -0
  31. package/dist/init/integrations/workflow-files.d.ts +23 -0
  32. package/dist/init/integrations/workflow-files.js +102 -0
  33. package/dist/init/integrations/workflow-files.js.map +1 -0
  34. package/dist/init/prompts.d.ts +9 -0
  35. package/dist/init/prompts.js +17 -0
  36. package/dist/init/prompts.js.map +1 -0
  37. package/dist/server/http.d.ts +21 -0
  38. package/dist/server/http.js +63 -0
  39. package/dist/server/http.js.map +1 -0
  40. package/dist/{server.d.ts → server/index.d.ts} +3 -1
  41. package/dist/{server.js → server/index.js} +7 -4
  42. package/dist/server/index.js.map +1 -0
  43. package/dist/shared/constants.d.ts +27 -0
  44. package/dist/shared/constants.js +26 -0
  45. package/dist/shared/constants.js.map +1 -0
  46. package/dist/shared/errors.d.ts +17 -0
  47. package/dist/shared/errors.js +28 -0
  48. package/dist/shared/errors.js.map +1 -0
  49. package/dist/shared/types.d.ts +29 -0
  50. package/dist/shared/types.js +17 -0
  51. package/dist/shared/types.js.map +1 -0
  52. package/dist/{context-store.d.ts → store/context-store.d.ts} +2 -16
  53. package/dist/store/context-store.js +147 -0
  54. package/dist/store/context-store.js.map +1 -0
  55. package/dist/store/index-builder.d.ts +32 -0
  56. package/dist/store/index-builder.js +162 -0
  57. package/dist/store/index-builder.js.map +1 -0
  58. package/dist/validation/index.d.ts +16 -0
  59. package/dist/validation/index.js +39 -0
  60. package/dist/validation/index.js.map +1 -0
  61. package/dist/{validation.d.ts → validation/write-guard.d.ts} +0 -16
  62. package/dist/{validation.js → validation/write-guard.js} +2 -38
  63. package/dist/validation/write-guard.js.map +1 -0
  64. package/package.json +2 -2
  65. package/dist/config.js +0 -145
  66. package/dist/config.js.map +0 -1
  67. package/dist/context-store.js +0 -296
  68. package/dist/context-store.js.map +0 -1
  69. package/dist/server.js.map +0 -1
  70. package/dist/types.d.ts +0 -56
  71. package/dist/types.js +0 -63
  72. package/dist/types.js.map +0 -1
  73. package/dist/validation.js.map +0 -1
@@ -0,0 +1,162 @@
1
+ import { readdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { INDEX_FILENAME, STATUS_BADGES } from "../shared/constants.js";
4
+ import { isNodeError } from "../shared/errors.js";
5
+ /**
6
+ * Parses YAML frontmatter from markdown content.
7
+ * Returns extracted fields or an empty object when no frontmatter is present.
8
+ * Uses simple line-by-line parsing — no external YAML dependency.
9
+ */
10
+ export function parseFrontmatter(content) {
11
+ const trimmed = content.trim();
12
+ if (!trimmed.startsWith("---")) {
13
+ return {};
14
+ }
15
+ const endIdx = trimmed.indexOf("---", 3);
16
+ if (endIdx === -1) {
17
+ return {};
18
+ }
19
+ const block = trimmed.slice(3, endIdx);
20
+ const result = {};
21
+ for (const line of block.split("\n")) {
22
+ const trimmedLine = line.trim();
23
+ if (!trimmedLine || trimmedLine.startsWith("#"))
24
+ continue;
25
+ const colonIdx = trimmedLine.indexOf(":");
26
+ if (colonIdx === -1)
27
+ continue;
28
+ const key = trimmedLine.slice(0, colonIdx).trim();
29
+ let value = trimmedLine.slice(colonIdx + 1).trim();
30
+ // Strip surrounding quotes
31
+ if ((value.startsWith('"') && value.endsWith('"')) ||
32
+ (value.startsWith("'") && value.endsWith("'"))) {
33
+ value = value.slice(1, -1);
34
+ }
35
+ if (key === "description") {
36
+ result.description = value;
37
+ }
38
+ else if (key === "status" &&
39
+ (value === "active" || value === "deprecated" || value === "superseded")) {
40
+ result.status = value;
41
+ }
42
+ else if (key === "supersedes") {
43
+ result.supersedes = value;
44
+ }
45
+ else if (key === "superseded_by") {
46
+ result.superseded_by = value;
47
+ }
48
+ }
49
+ return result;
50
+ }
51
+ /**
52
+ * Extracts a short description from markdown content.
53
+ * Strategy: frontmatter description → first heading → first paragraph → fallback.
54
+ * @param content - Raw markdown content
55
+ * @returns Truncated description (max 120 chars)
56
+ */
57
+ export function extractDescription(content) {
58
+ const trimmed = content.trim();
59
+ if (!trimmed) {
60
+ return "No summary available.";
61
+ }
62
+ const fm = parseFrontmatter(content);
63
+ if (fm.description) {
64
+ return fm.description.slice(0, 120);
65
+ }
66
+ const lines = trimmed.split("\n");
67
+ // Fall back to the first heading, then the first paragraph line.
68
+ for (const line of lines) {
69
+ const headingMatch = line.match(/^#\s+(.+)$/);
70
+ if (headingMatch?.[1]) {
71
+ return headingMatch[1].slice(0, 120);
72
+ }
73
+ }
74
+ for (const line of lines) {
75
+ const stripped = line.trim();
76
+ if (stripped && !stripped.startsWith("#")) {
77
+ return stripped.length > 120 ? stripped.slice(0, 120) + "..." : stripped;
78
+ }
79
+ }
80
+ return "No summary available.";
81
+ }
82
+ /** Formats a byte count as a compact human-readable size string. */
83
+ function formatSize(sizeBytes) {
84
+ return sizeBytes >= 1024
85
+ ? `${(sizeBytes / 1024).toFixed(1)} KB`
86
+ : `${sizeBytes} B`;
87
+ }
88
+ /**
89
+ * Rebuilds the auto-generated index.md file in the context directory.
90
+ * Scans all topic files, extracts metadata, and writes a compact index.
91
+ * @param contextDir - Absolute path to the .opencontext directory
92
+ * @returns The generated index markdown content ("" when no topics exist)
93
+ */
94
+ export async function buildContextIndex(contextDir) {
95
+ let entries;
96
+ try {
97
+ entries = await readdir(contextDir, { withFileTypes: true });
98
+ }
99
+ catch (error) {
100
+ if (isNodeError(error) && error.code === "ENOENT") {
101
+ return "";
102
+ }
103
+ throw error;
104
+ }
105
+ const topicFiles = entries
106
+ .filter((entry) => entry.isFile() &&
107
+ entry.name.endsWith(".md") &&
108
+ entry.name !== INDEX_FILENAME &&
109
+ entry.name !== "README.md")
110
+ .sort((a, b) => a.name.localeCompare(b.name));
111
+ if (topicFiles.length === 0) {
112
+ return "";
113
+ }
114
+ const topicEntries = [];
115
+ for (const entry of topicFiles) {
116
+ const filePath = path.join(contextDir, entry.name);
117
+ const content = await readFile(filePath, "utf8");
118
+ const fileStat = await stat(filePath);
119
+ const topic = entry.name.slice(0, -".md".length);
120
+ const fm = parseFrontmatter(content);
121
+ topicEntries.push({
122
+ topic,
123
+ filename: entry.name,
124
+ description: extractDescription(content),
125
+ date: fileStat.mtime.toISOString().slice(0, 10),
126
+ sizeBytes: Buffer.byteLength(content, "utf8"),
127
+ status: fm.status,
128
+ supersedes: fm.supersedes,
129
+ superseded_by: fm.superseded_by,
130
+ });
131
+ }
132
+ const lines = [
133
+ "# Project Context Index",
134
+ "<!-- AUTO-GENERATED BY OPENCONTEXT - DO NOT EDIT MANUALLY -->",
135
+ "",
136
+ "Available context topics in this project:",
137
+ "",
138
+ ];
139
+ for (const t of topicEntries) {
140
+ const badge = t.status && t.status !== "active"
141
+ ? ` ${STATUS_BADGES.get(t.status) ?? `[${t.status.toUpperCase()}]`}`
142
+ : "";
143
+ const supersedesNote = t.supersedes
144
+ ? ` (supersedes: \`${t.supersedes}\`)`
145
+ : "";
146
+ const supersededByNote = t.superseded_by
147
+ ? ` (superseded by: \`${t.superseded_by}\`)`
148
+ : "";
149
+ lines.push(`- **${t.topic}**${badge} (\`${t.filename}\`) - Updated: ${t.date} (${formatSize(t.sizeBytes)})${supersedesNote}${supersededByNote}`);
150
+ lines.push(` > ${t.description}`);
151
+ }
152
+ lines.push("");
153
+ lines.push("---");
154
+ lines.push("*To read a specific context topic, call `read_context` with the topic name.*");
155
+ lines.push("");
156
+ lines.push("*OpenContext is open-source. Consider starring on [GitHub](https://github.com/slxca/opencontext).*");
157
+ const indexContent = lines.join("\n");
158
+ const indexPath = path.join(contextDir, INDEX_FILENAME);
159
+ await writeFile(indexPath, indexContent, "utf8");
160
+ return indexContent;
161
+ }
162
+ //# sourceMappingURL=index-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-builder.js","sourceRoot":"","sources":["../../src/store/index-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Validates a topic string for safe filesystem operations.
3
+ * @param topicInput - The topic string to validate
4
+ * @returns The trimmed, validated topic
5
+ * @throws UserInputError if the topic is invalid
6
+ */
7
+ export declare function validateTopic(topicInput: string): string;
8
+ /**
9
+ * Sanitizes a topic path to prevent path traversal attacks.
10
+ * Ensures the resolved path stays within the context directory.
11
+ * @param contextDir - The absolute path to the context directory
12
+ * @param topic - The topic name
13
+ * @returns The sanitized absolute path to the topic file
14
+ * @throws UserInputError if path traversal is detected
15
+ */
16
+ export declare function sanitizeTopicPath(contextDir: string, topic: string): string;
@@ -0,0 +1,39 @@
1
+ import * as path from "node:path";
2
+ import { TOPIC_PATTERN } from "../shared/constants.js";
3
+ import { UserInputError } from "../shared/errors.js";
4
+ /**
5
+ * Validates a topic string for safe filesystem operations.
6
+ * @param topicInput - The topic string to validate
7
+ * @returns The trimmed, validated topic
8
+ * @throws UserInputError if the topic is invalid
9
+ */
10
+ export function validateTopic(topicInput) {
11
+ const topic = topicInput.trim();
12
+ if (!TOPIC_PATTERN.test(topic)) {
13
+ throw new UserInputError("Topic must be snake_case or kebab-case using lowercase letters, numbers, underscores, or hyphens.");
14
+ }
15
+ return topic;
16
+ }
17
+ /**
18
+ * Sanitizes a topic path to prevent path traversal attacks.
19
+ * Ensures the resolved path stays within the context directory.
20
+ * @param contextDir - The absolute path to the context directory
21
+ * @param topic - The topic name
22
+ * @returns The sanitized absolute path to the topic file
23
+ * @throws UserInputError if path traversal is detected
24
+ */
25
+ export function sanitizeTopicPath(contextDir, topic) {
26
+ // Quick rejection of obvious path traversal attempts
27
+ if (topic.includes("..") || path.isAbsolute(topic) || topic.startsWith(".") || topic.includes("/")) {
28
+ throw new UserInputError("Path traversal detected: topic must not contain '..', '/', or absolute paths.");
29
+ }
30
+ const filePath = path.join(contextDir, `${topic}.md`);
31
+ const resolvedPath = path.resolve(filePath);
32
+ const resolvedContextDir = path.resolve(contextDir);
33
+ // Double-check resolved path is still within .opencontext directory
34
+ if (!resolvedPath.startsWith(resolvedContextDir + path.sep) && resolvedPath !== resolvedContextDir) {
35
+ throw new UserInputError("Path traversal detected: topic must not contain '..' or absolute paths.");
36
+ }
37
+ return resolvedPath;
38
+ }
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAEhC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,cAAc,CACtB,mGAAmG,CACpG,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,KAAa;IACjE,qDAAqD;IACrD,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACnG,MAAM,IAAI,cAAc,CAAC,+EAA+E,CAAC,CAAC;IAC5G,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,KAAK,KAAK,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5C,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAEpD,oEAAoE;IACpE,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,KAAK,kBAAkB,EAAE,CAAC;QACnG,MAAM,IAAI,cAAc,CAAC,yEAAyE,CAAC,CAAC;IACtG,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC"}
@@ -20,22 +20,6 @@ export interface GuardResult {
20
20
  /** Error code for programmatic handling */
21
21
  code?: "EMPTY_CONTENT" | "PAYLOAD_TOO_LARGE" | "INVALID_TOPIC" | "PATH_TRAVERSAL" | "FORBIDDEN_PATTERN" | "RESERVED_TOPIC";
22
22
  }
23
- /**
24
- * Validates a topic string for safe filesystem operations.
25
- * @param topicInput - The topic string to validate
26
- * @returns The trimmed, validated topic
27
- * @throws UserInputError if the topic is invalid
28
- */
29
- export declare function validateTopic(topicInput: string): string;
30
- /**
31
- * Sanitizes a topic path to prevent path traversal attacks.
32
- * Ensures the resolved path stays within the context directory.
33
- * @param contextDir - The absolute path to the context directory
34
- * @param topic - The topic name
35
- * @returns The sanitized absolute path to the topic file
36
- * @throws UserInputError if path traversal is detected
37
- */
38
- export declare function sanitizeTopicPath(contextDir: string, topic: string): string;
39
23
  /**
40
24
  * Validates a write payload for context storage.
41
25
  * Checks topic validity, content safety, size limits, and forbidden patterns.
@@ -1,5 +1,4 @@
1
- import * as path from "node:path";
2
- import { RESERVED_TOPICS, TOPIC_PATTERN, UserInputError } from "./types.js";
1
+ import { RESERVED_TOPICS, TOPIC_PATTERN } from "../shared/constants.js";
3
2
  /**
4
3
  * Forbidden patterns that indicate prompt injection attempts.
5
4
  * These are checked case-insensitively against the content.
@@ -9,41 +8,6 @@ const FORBIDDEN_PATTERNS = [
9
8
  /system\s*:\s*override/i,
10
9
  /bypass\s+(safety|guardrails?|system\s+prompt)/i,
11
10
  ];
12
- /**
13
- * Validates a topic string for safe filesystem operations.
14
- * @param topicInput - The topic string to validate
15
- * @returns The trimmed, validated topic
16
- * @throws UserInputError if the topic is invalid
17
- */
18
- export function validateTopic(topicInput) {
19
- const topic = topicInput.trim();
20
- if (!TOPIC_PATTERN.test(topic)) {
21
- throw new UserInputError("Topic must be snake_case or kebab-case using lowercase letters, numbers, underscores, or hyphens.");
22
- }
23
- return topic;
24
- }
25
- /**
26
- * Sanitizes a topic path to prevent path traversal attacks.
27
- * Ensures the resolved path stays within the context directory.
28
- * @param contextDir - The absolute path to the context directory
29
- * @param topic - The topic name
30
- * @returns The sanitized absolute path to the topic file
31
- * @throws UserInputError if path traversal is detected
32
- */
33
- export function sanitizeTopicPath(contextDir, topic) {
34
- // Quick rejection of obvious path traversal attempts
35
- if (topic.includes("..") || path.isAbsolute(topic) || topic.startsWith(".") || topic.includes("/")) {
36
- throw new UserInputError("Path traversal detected: topic must not contain '..', '/', or absolute paths.");
37
- }
38
- const filePath = path.join(contextDir, `${topic}.md`);
39
- const resolvedPath = path.resolve(filePath);
40
- const resolvedContextDir = path.resolve(contextDir);
41
- // Double-check resolved path is still within .opencontext directory
42
- if (!resolvedPath.startsWith(resolvedContextDir + path.sep) && resolvedPath !== resolvedContextDir) {
43
- throw new UserInputError("Path traversal detected: topic must not contain '..' or absolute paths.");
44
- }
45
- return resolvedPath;
46
- }
47
11
  /**
48
12
  * Validates a write payload for context storage.
49
13
  * Checks topic validity, content safety, size limits, and forbidden patterns.
@@ -103,4 +67,4 @@ export function validateWritePayload(topic, content, options) {
103
67
  }
104
68
  return { allowed: true };
105
69
  }
106
- //# sourceMappingURL=validation.js.map
70
+ //# sourceMappingURL=write-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-guard.js","sourceRoot":"","sources":["../../src/validation/write-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AA0BxE;;;GAGG;AACH,MAAM,kBAAkB,GAAa;IACnC,oDAAoD;IACpD,wBAAwB;IACxB,gDAAgD;CACjD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAa,EACb,OAAe,EACf,OAAsB;IAEtB,MAAM,IAAI,GAA2B;QACnC,aAAa,EAAE,OAAO,EAAE,aAAa,IAAI,EAAE;QAC3C,UAAU,EAAE,OAAO,EAAE,UAAU,IAAI,KAAK;QACxC,kBAAkB,EAAE,OAAO,EAAE,kBAAkB,IAAI,IAAI;KACxD,CAAC;IAEF,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QACtC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,mGAAmG;YAC3G,IAAI,EAAE,eAAe;SACtB,CAAC;IACJ,CAAC;IAED,IAAI,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QACtC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,IAAI,YAAY,8DAA8D;YACtF,IAAI,EAAE,gBAAgB;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,4DAA4D;YACpE,IAAI,EAAE,eAAe;SACtB,CAAC;IACJ,CAAC;IAED,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAC3C,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACxD,IAAI,YAAY,GAAG,QAAQ,EAAE,CAAC;QAC5B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,iBAAiB,YAAY,yCAAyC,QAAQ,UAAU;YAChG,IAAI,EAAE,mBAAmB;SAC1B,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC5B,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;YACzC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1B,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,uCAAuC,OAAO,CAAC,MAAM,EAAE;oBAC/D,IAAI,EAAE,mBAAmB;iBAC1B,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencontext-mcp",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "A Model Context Protocol server for persistent project-specific AI agent context.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "examples"
13
13
  ],
14
14
  "scripts": {
15
- "build": "tsc -p tsconfig.json",
15
+ "build": "tsc -p tsconfig.json && chmod +x dist/index.js",
16
16
  "typecheck": "tsc --noEmit -p tsconfig.json",
17
17
  "test": "vitest run",
18
18
  "test:watch": "vitest",
package/dist/config.js DELETED
@@ -1,145 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import * as path from "node:path";
3
- export const DEFAULT_CONFIG = {
4
- path: ".opencontext",
5
- readOnly: false,
6
- disabled: false,
7
- autoIndex: true,
8
- history: {
9
- enabled: false,
10
- maxBackupsPerTopic: 5,
11
- retentionDays: 7,
12
- },
13
- guard: {
14
- enabled: true,
15
- maxFileSizeKb: 50,
16
- strictPatternCheck: true,
17
- },
18
- };
19
- const CONFIG_CANDIDATES = [".opencontext.jsonc", ".opencontext.json"];
20
- /**
21
- * Strips JSONC content into parseable JSON by removing comments and trailing commas.
22
- * Handles single-line (//), multi-line (/* * /), and trailing commas.
23
- * Trailing comma removal is token-aware to avoid modifying string literals.
24
- */
25
- function stripJsonComments(raw) {
26
- let result = "";
27
- let i = 0;
28
- const len = raw.length;
29
- while (i < len) {
30
- const ch = raw[i];
31
- // Strings: copy verbatim (including escaped characters)
32
- if (ch === '"') {
33
- result += ch;
34
- i++;
35
- while (i < len && raw[i] !== '"') {
36
- if (raw[i] === "\\") {
37
- result += raw[i];
38
- i++;
39
- }
40
- if (i < len) {
41
- result += raw[i];
42
- i++;
43
- }
44
- }
45
- if (i < len) {
46
- result += raw[i]; // closing quote
47
- i++;
48
- }
49
- continue;
50
- }
51
- // Single-line comment: skip until newline
52
- if (ch === "/" && i + 1 < len && raw[i + 1] === "/") {
53
- while (i < len && raw[i] !== "\n") {
54
- i++;
55
- }
56
- continue;
57
- }
58
- // Multi-line comment: skip until */
59
- if (ch === "/" && i + 1 < len && raw[i + 1] === "*") {
60
- i += 2;
61
- while (i < len - 1 && !(raw[i] === "*" && raw[i + 1] === "/")) {
62
- i++;
63
- }
64
- i += 2; // skip */
65
- continue;
66
- }
67
- // Trailing comma: skip comma if followed by optional whitespace/comments then } or ]
68
- if (ch === ",") {
69
- let j = i + 1;
70
- // Skip whitespace and comments to find the next meaningful token
71
- while (j < len) {
72
- if (raw[j] === " " || raw[j] === "\t" || raw[j] === "\n" || raw[j] === "\r") {
73
- j++;
74
- }
75
- else if (raw[j] === "/" && j + 1 < len && raw[j + 1] === "/") {
76
- while (j < len && raw[j] !== "\n")
77
- j++;
78
- }
79
- else if (raw[j] === "/" && j + 1 < len && raw[j + 1] === "*") {
80
- j += 2;
81
- while (j < len - 1 && !(raw[j] === "*" && raw[j + 1] === "/"))
82
- j++;
83
- j += 2;
84
- }
85
- else {
86
- break;
87
- }
88
- }
89
- if (j < len && (raw[j] === "}" || raw[j] === "]")) {
90
- i = j; // skip comma and whitespace/comments, continue from closing bracket
91
- continue;
92
- }
93
- // Not a trailing comma — keep it
94
- result += ch;
95
- i++;
96
- continue;
97
- }
98
- result += ch;
99
- i++;
100
- }
101
- return result;
102
- }
103
- function deepMerge(target, source) {
104
- const result = { ...target };
105
- for (const key of Object.keys(source)) {
106
- const sourceVal = source[key];
107
- const targetVal = result[key];
108
- if (sourceVal !== null &&
109
- typeof sourceVal === "object" &&
110
- !Array.isArray(sourceVal) &&
111
- targetVal !== null &&
112
- typeof targetVal === "object" &&
113
- !Array.isArray(targetVal)) {
114
- result[key] = deepMerge(targetVal, sourceVal);
115
- }
116
- else if (sourceVal !== undefined) {
117
- result[key] = sourceVal;
118
- }
119
- }
120
- return result;
121
- }
122
- /**
123
- * Loads configuration from .opencontext.jsonc or .opencontext.json in the given directory.
124
- * Falls back to DEFAULT_CONFIG if no file exists or parsing fails.
125
- */
126
- export async function loadConfig(cwd = process.cwd()) {
127
- for (const filename of CONFIG_CANDIDATES) {
128
- const filePath = path.join(cwd, filename);
129
- try {
130
- const raw = await readFile(filePath, "utf8");
131
- const cleaned = stripJsonComments(raw);
132
- const parsed = JSON.parse(cleaned);
133
- return deepMerge(DEFAULT_CONFIG, parsed);
134
- }
135
- catch (error) {
136
- if (error.code === "ENOENT") {
137
- continue; // file doesn't exist, try next candidate
138
- }
139
- console.error(`OpenContext: failed to parse ${filename}: ${error.message}`);
140
- return DEFAULT_CONFIG;
141
- }
142
- }
143
- return DEFAULT_CONFIG;
144
- }
145
- //# sourceMappingURL=config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}