opencontext-mcp 1.0.0 → 1.2.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.
@@ -1,48 +1,127 @@
1
- import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { CONTEXT_DIRECTORY_NAME, UserInputError, isNodeError } from "./types.js";
4
- import { validateTopic } from "./validation.js";
1
+ import { mkdir, readdir, readFile, writeFile, stat, lstat, unlink } from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { INDEX_FILENAME, UserInputError, isNodeError, STATUS_BADGES } from "./types.js";
4
+ import { validateTopic, validateWritePayload, sanitizeTopicPath } from "./validation.js";
5
+ import { DEFAULT_CONFIG } from "./config.js";
6
+ /**
7
+ * Manages context file storage operations.
8
+ * Handles reading, writing, and listing context files in the configured directory.
9
+ */
5
10
  export class ContextStore {
6
11
  basePath;
7
- constructor(basePath = process.cwd()) {
12
+ config;
13
+ contextDir;
14
+ constructor(basePath = process.cwd(), config = DEFAULT_CONFIG) {
8
15
  this.basePath = basePath;
16
+ this.config = config;
17
+ this.contextDir = path.join(this.basePath, this.config.path);
9
18
  }
19
+ /** Returns the absolute path to the context directory. */
10
20
  getContextDirectory() {
11
- return path.join(this.basePath, CONTEXT_DIRECTORY_NAME);
21
+ return this.contextDir;
12
22
  }
23
+ /** Returns the absolute path to a topic file. */
13
24
  getTopicFilePath(topic) {
14
- return path.join(this.getContextDirectory(), `${topic}.md`);
25
+ return path.join(this.contextDir, `${topic}.md`);
15
26
  }
27
+ /**
28
+ * Saves context content to a topic file.
29
+ * Validates payload using WriteGuard before writing.
30
+ * @param topicInput - Topic name (will be validated and trimmed)
31
+ * @param content - Markdown content to save
32
+ * @returns Success message with file location
33
+ * @throws UserInputError if validation fails
34
+ */
16
35
  async saveContext(topicInput, content) {
36
+ const guardResult = validateWritePayload(topicInput, content, {
37
+ maxFileSizeKb: this.config.guard.maxFileSizeKb,
38
+ strictPatternCheck: this.config.guard.strictPatternCheck,
39
+ });
40
+ if (!guardResult.allowed) {
41
+ console.error(`WriteGuard Rejected: ${guardResult.reason} (Code: ${guardResult.code})`);
42
+ throw new UserInputError(`WriteGuard Rejected: ${guardResult.reason} (Code: ${guardResult.code})`);
43
+ }
17
44
  const topic = validateTopic(topicInput);
18
- const contextDirectory = this.getContextDirectory();
19
- const filePath = this.getTopicFilePath(topic);
20
- await mkdir(contextDirectory, { recursive: true });
45
+ const filePath = sanitizeTopicPath(this.contextDir, topic);
46
+ // Reject writes to existing symlinks — prevents symlink traversal attacks
47
+ try {
48
+ const fileStat = await lstat(filePath);
49
+ if (fileStat.isSymbolicLink()) {
50
+ throw new UserInputError(`Refusing to overwrite symlink at ${this.config.path}/${topic}.md. Delete the symlink first.`);
51
+ }
52
+ }
53
+ catch (error) {
54
+ if (isNodeError(error) && error.code === "ENOENT") {
55
+ // File does not exist yet — normal case, continue
56
+ }
57
+ else {
58
+ throw error;
59
+ }
60
+ }
61
+ await mkdir(this.contextDir, { recursive: true });
21
62
  await writeFile(filePath, content, "utf8");
22
- return `Saved context topic "${topic}" to ${CONTEXT_DIRECTORY_NAME}/${topic}.md.`;
63
+ return `Saved context topic "${topic}" to ${this.config.path}/${topic}.md.`;
23
64
  }
65
+ /**
66
+ * Reads context content from a topic file, or lists all topics if none specified.
67
+ * @param topicInput - Optional topic name to read
68
+ * @returns Topic content or list of available topics
69
+ * @throws UserInputError if topic doesn't exist or is invalid
70
+ */
24
71
  async readContext(topicInput) {
25
72
  if (topicInput !== undefined) {
26
73
  const topic = validateTopic(topicInput);
27
74
  try {
28
- return await readFile(this.getTopicFilePath(topic), "utf8");
75
+ const filePath = sanitizeTopicPath(this.contextDir, topic);
76
+ return await readFile(filePath, "utf8");
29
77
  }
30
78
  catch (error) {
31
79
  if (isNodeError(error) && error.code === "ENOENT") {
32
- throw new UserInputError(`No context found for topic "${topic}" at ${CONTEXT_DIRECTORY_NAME}/${topic}.md.`);
80
+ throw new UserInputError(`No context found for topic "${topic}" at ${this.config.path}/${topic}.md.`);
33
81
  }
34
82
  throw error;
35
83
  }
36
84
  }
37
85
  const topics = await this.listTopics();
38
86
  if (topics.length === 0) {
39
- return `No OpenContext topics found in ${CONTEXT_DIRECTORY_NAME}/. Use save_context to create one.`;
87
+ return `No OpenContext topics found in ${this.config.path}/. Use save_context to create one.`;
88
+ }
89
+ if (!this.config.autoIndex) {
90
+ return topics.map((t) => `- ${t}`).join("\n");
91
+ }
92
+ return this.rebuildContextIndex();
93
+ }
94
+ /**
95
+ * Deletes a context topic file from the context directory.
96
+ * @param topicInput - Topic name to delete
97
+ * @returns Success message with confirmation
98
+ * @throws UserInputError if topic doesn't exist or is invalid
99
+ */
100
+ async deleteContext(topicInput) {
101
+ const topic = validateTopic(topicInput);
102
+ const filePath = sanitizeTopicPath(this.contextDir, topic);
103
+ try {
104
+ await unlink(filePath);
105
+ }
106
+ catch (error) {
107
+ if (isNodeError(error) && error.code === "ENOENT") {
108
+ throw new UserInputError(`No context found for topic "${topic}" at ${this.config.path}/${topic}.md.`);
109
+ }
110
+ throw error;
111
+ }
112
+ if (this.config.autoIndex) {
113
+ await this.rebuildContextIndex();
40
114
  }
41
- return `Available OpenContext topics:\n\n${topics.map((topic) => `- ${topic}`).join("\n")}`;
115
+ return `Deleted context topic "${topic}" from ${this.config.path}/${topic}.md.`;
42
116
  }
117
+ /**
118
+ * Lists all available context topics.
119
+ * Scans the context directory for .md files and returns sorted topic names.
120
+ * @returns Sorted array of topic names
121
+ */
43
122
  async listTopics() {
44
123
  try {
45
- const entries = await readdir(this.getContextDirectory(), { withFileTypes: true });
124
+ const entries = await readdir(this.contextDir, { withFileTypes: true });
46
125
  return entries
47
126
  .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
48
127
  .map((entry) => entry.name.slice(0, -".md".length))
@@ -55,5 +134,162 @@ export class ContextStore {
55
134
  throw error;
56
135
  }
57
136
  }
137
+ /**
138
+ * Parses YAML frontmatter from markdown content.
139
+ * Returns extracted fields or empty object if no frontmatter found.
140
+ * Uses simple line-by-line parsing — no external YAML dependency.
141
+ */
142
+ parseFrontmatter(content) {
143
+ const trimmed = content.trim();
144
+ if (!trimmed.startsWith("---")) {
145
+ return {};
146
+ }
147
+ const endIdx = trimmed.indexOf("---", 3);
148
+ if (endIdx === -1) {
149
+ return {};
150
+ }
151
+ const block = trimmed.slice(3, endIdx);
152
+ const result = {};
153
+ for (const line of block.split("\n")) {
154
+ const trimmedLine = line.trim();
155
+ if (!trimmedLine || trimmedLine.startsWith("#"))
156
+ continue;
157
+ const colonIdx = trimmedLine.indexOf(":");
158
+ if (colonIdx === -1)
159
+ continue;
160
+ const key = trimmedLine.slice(0, colonIdx).trim();
161
+ let value = trimmedLine.slice(colonIdx + 1).trim();
162
+ // Strip surrounding quotes
163
+ if ((value.startsWith('"') && value.endsWith('"')) ||
164
+ (value.startsWith("'") && value.endsWith("'"))) {
165
+ value = value.slice(1, -1);
166
+ }
167
+ if (key === "description") {
168
+ result.description = value;
169
+ }
170
+ else if (key === "status" &&
171
+ (value === "active" || value === "deprecated" || value === "superseded")) {
172
+ result.status = value;
173
+ }
174
+ else if (key === "supersedes") {
175
+ result.supersedes = value;
176
+ }
177
+ else if (key === "superseded_by") {
178
+ result.superseded_by = value;
179
+ }
180
+ }
181
+ return result;
182
+ }
183
+ /**
184
+ * Extracts a short description from markdown content.
185
+ * Strategy: frontmatter description → first heading → first paragraph → fallback.
186
+ * @param content - Raw markdown content
187
+ * @returns Truncated description (max 120 chars)
188
+ */
189
+ extractDescription(content) {
190
+ const trimmed = content.trim();
191
+ if (!trimmed) {
192
+ return "No summary available.";
193
+ }
194
+ // Use structured frontmatter parser
195
+ const fm = this.parseFrontmatter(content);
196
+ if (fm.description) {
197
+ return fm.description.slice(0, 120);
198
+ }
199
+ const lines = trimmed.split("\n");
200
+ // Strategy B: First heading
201
+ for (const line of lines) {
202
+ const headingMatch = line.match(/^#\s+(.+)$/);
203
+ if (headingMatch?.[1]) {
204
+ return headingMatch[1].slice(0, 120);
205
+ }
206
+ }
207
+ // Strategy C: First non-empty paragraph line
208
+ for (const line of lines) {
209
+ const stripped = line.trim();
210
+ if (stripped && !stripped.startsWith("#")) {
211
+ return stripped.length > 120 ? stripped.slice(0, 120) + "..." : stripped;
212
+ }
213
+ }
214
+ return "No summary available.";
215
+ }
216
+ /**
217
+ * Rebuilds the auto-generated index.md file in the context directory.
218
+ * Scans all topic files, extracts metadata, and writes a compact index.
219
+ * @returns The generated index markdown content
220
+ */
221
+ async rebuildContextIndex() {
222
+ let entries;
223
+ try {
224
+ entries = await readdir(this.contextDir, { withFileTypes: true });
225
+ }
226
+ catch (error) {
227
+ if (isNodeError(error) && error.code === "ENOENT") {
228
+ return "";
229
+ }
230
+ throw error;
231
+ }
232
+ const topicFiles = entries
233
+ .filter((entry) => entry.isFile() &&
234
+ entry.name.endsWith(".md") &&
235
+ entry.name !== INDEX_FILENAME &&
236
+ entry.name !== "README.md")
237
+ .sort((a, b) => a.name.localeCompare(b.name));
238
+ if (topicFiles.length === 0) {
239
+ return "";
240
+ }
241
+ const topicEntries = [];
242
+ for (const entry of topicFiles) {
243
+ const filePath = path.join(this.contextDir, entry.name);
244
+ const content = await readFile(filePath, "utf8");
245
+ const fileStat = await stat(filePath);
246
+ const topic = entry.name.slice(0, -".md".length);
247
+ const description = this.extractDescription(content);
248
+ const date = fileStat.mtime.toISOString().slice(0, 10);
249
+ const sizeBytes = Buffer.byteLength(content, "utf8");
250
+ const fm = this.parseFrontmatter(content);
251
+ topicEntries.push({
252
+ topic,
253
+ filename: entry.name,
254
+ description,
255
+ date,
256
+ sizeBytes,
257
+ status: fm.status,
258
+ supersedes: fm.supersedes,
259
+ superseded_by: fm.superseded_by,
260
+ });
261
+ }
262
+ const lines = [
263
+ "# Project Context Index",
264
+ "<!-- AUTO-GENERATED BY OPENCONTEXT - DO NOT EDIT MANUALLY -->",
265
+ "",
266
+ "Available context topics in this project:",
267
+ "",
268
+ ];
269
+ for (const t of topicEntries) {
270
+ const sizeStr = t.sizeBytes >= 1024
271
+ ? `${(t.sizeBytes / 1024).toFixed(1)} KB`
272
+ : `${t.sizeBytes} B`;
273
+ const badge = t.status && t.status !== "active"
274
+ ? ` ${STATUS_BADGES.get(t.status) ?? `[${t.status.toUpperCase()}]`}`
275
+ : "";
276
+ const supersedesNote = t.supersedes
277
+ ? ` (supersedes: \`${t.supersedes}\`)`
278
+ : "";
279
+ const supersededByNote = t.superseded_by
280
+ ? ` (superseded by: \`${t.superseded_by}\`)`
281
+ : "";
282
+ lines.push(`- **${t.topic}**${badge} (\`${t.filename}\`) - Updated: ${t.date} (${sizeStr})${supersedesNote}${supersededByNote}`);
283
+ lines.push(` > ${t.description}`);
284
+ }
285
+ lines.push("");
286
+ lines.push("---");
287
+ lines.push("*To read a specific context topic, call `read_context` with the topic name.*");
288
+ lines.push("");
289
+ const indexContent = lines.join("\n");
290
+ const indexPath = path.join(this.contextDir, INDEX_FILENAME);
291
+ await writeFile(indexPath, indexContent, "utf8");
292
+ return indexContent;
293
+ }
58
294
  }
59
295
  //# sourceMappingURL=context-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-store.js","sourceRoot":"","sources":["../src/context-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context-store.js","sourceRoot":"","sources":["../src/context-store.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,8 +2,12 @@
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { createOpenContextServer } from "./server.js";
4
4
  import { SERVER_NAME, getErrorMessage } from "./types.js";
5
+ /**
6
+ * Main entry point for the OpenContext MCP server.
7
+ * Creates server instance and connects to stdio transport.
8
+ */
5
9
  async function main() {
6
- const server = createOpenContextServer();
10
+ const server = await createOpenContextServer();
7
11
  const transport = new StdioServerTransport();
8
12
  await server.connect(transport);
9
13
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE1D,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,uBAAuB,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,mBAAmB,WAAW,KAAK,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE1D;;;GAGG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,MAAM,uBAAuB,EAAE,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,mBAAmB,WAAW,KAAK,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
package/dist/server.d.ts CHANGED
@@ -1,2 +1,9 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- export declare function createOpenContextServer(basePath?: string): McpServer;
2
+ import { type ResolvedConfig } from "./config.js";
3
+ /**
4
+ * Creates and configures the OpenContext MCP server.
5
+ * Registers all available tools (save_context, read_context).
6
+ * @param basePath - Optional base directory (defaults to cwd)
7
+ * @param config - Optional pre-loaded config (loads from disk if omitted)
8
+ */
9
+ export declare function createOpenContextServer(basePath?: string, config?: ResolvedConfig): Promise<McpServer>;
package/dist/server.js CHANGED
@@ -2,15 +2,23 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
  import { ContextStore } from "./context-store.js";
4
4
  import { SERVER_NAME, SERVER_VERSION, getErrorMessage, textResult } from "./types.js";
5
- export function createOpenContextServer(basePath) {
6
- const store = new ContextStore(basePath);
5
+ import { loadConfig } from "./config.js";
6
+ /**
7
+ * Creates and configures the OpenContext MCP server.
8
+ * Registers all available tools (save_context, read_context).
9
+ * @param basePath - Optional base directory (defaults to cwd)
10
+ * @param config - Optional pre-loaded config (loads from disk if omitted)
11
+ */
12
+ export async function createOpenContextServer(basePath, config) {
13
+ const resolvedConfig = config ?? await loadConfig(basePath);
14
+ const store = new ContextStore(basePath, resolvedConfig);
7
15
  const server = new McpServer({
8
16
  name: SERVER_NAME,
9
17
  version: SERVER_VERSION,
10
18
  });
11
19
  server.registerTool("save_context", {
12
20
  title: "Save Context",
13
- description: "Persist markdown project context, architectural rules, or decisions into .opencontext/<topic>.md in the current working directory.",
21
+ description: `Persist markdown project context, architectural rules, or decisions into ${resolvedConfig.path}/<topic>.md in the current working directory.`,
14
22
  inputSchema: {
15
23
  topic: z
16
24
  .string()
@@ -19,6 +27,12 @@ export function createOpenContextServer(basePath) {
19
27
  content: z.string().min(1).describe("Markdown content to save for this project topic."),
20
28
  },
21
29
  }, async ({ topic, content }) => {
30
+ if (resolvedConfig.disabled) {
31
+ return textResult("OpenContext is currently paused. Tool access is disabled via configuration.");
32
+ }
33
+ if (resolvedConfig.readOnly) {
34
+ return textResult("OpenContext is in read-only mode. Write operations are disabled via configuration.", true);
35
+ }
22
36
  try {
23
37
  const result = await store.saveContext(topic, content);
24
38
  return textResult(result);
@@ -38,6 +52,9 @@ export function createOpenContextServer(basePath) {
38
52
  .describe("Optional topic name in snake_case or kebab-case. Omit to list all saved topics."),
39
53
  },
40
54
  }, async ({ topic }) => {
55
+ if (resolvedConfig.disabled) {
56
+ return textResult("OpenContext is currently paused. Tool access is disabled via configuration.");
57
+ }
41
58
  try {
42
59
  const result = await store.readContext(topic);
43
60
  return textResult(result);
@@ -46,6 +63,30 @@ export function createOpenContextServer(basePath) {
46
63
  return textResult(`OpenContext error: ${getErrorMessage(error)}`, true);
47
64
  }
48
65
  });
66
+ server.registerTool("delete_context", {
67
+ title: "Delete Context",
68
+ description: "Delete a saved OpenContext topic. The topic file will be removed from disk.",
69
+ inputSchema: {
70
+ topic: z
71
+ .string()
72
+ .min(1)
73
+ .describe("Topic name in snake_case or kebab-case to delete."),
74
+ },
75
+ }, async ({ topic }) => {
76
+ if (resolvedConfig.disabled) {
77
+ return textResult("OpenContext is currently paused. Tool access is disabled via configuration.");
78
+ }
79
+ if (resolvedConfig.readOnly) {
80
+ return textResult("OpenContext is in read-only mode. Write operations are disabled via configuration.", true);
81
+ }
82
+ try {
83
+ const result = await store.deleteContext(topic);
84
+ return textResult(result);
85
+ }
86
+ catch (error) {
87
+ return textResult(`OpenContext error: ${getErrorMessage(error)}`, true);
88
+ }
89
+ });
49
90
  return server;
50
91
  }
51
92
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtF,MAAM,UAAU,uBAAuB,CAAC,QAAiB;IACvD,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC;IAEzC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,cAAc;KACxB,CAAC,CAAC;IAEH,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,oIAAoI;QACtI,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CACP,0FAA0F,CAC3F;YACH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,kDAAkD,CAAC;SACxF;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;QAC3B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACvD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,UAAU,CAAC,sBAAsB,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,yFAAyF;QAC3F,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,EAAE;iBACV,QAAQ,CACP,iFAAiF,CAClF;SACJ;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAClB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC9C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,UAAU,CAAC,sBAAsB,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,CACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,10 +1,42 @@
1
+ /** MCP server identifier used in protocol handshakes. */
1
2
  export declare const SERVER_NAME = "opencontext-mcp";
2
- export declare const SERVER_VERSION = "0.1.0";
3
- export declare const CONTEXT_DIRECTORY_NAME = ".opencontext";
3
+ /** Current server version for compatibility checks. */
4
+ export declare const SERVER_VERSION = "1.2.0";
5
+ /** Filename for the auto-generated context index. */
6
+ export declare const INDEX_FILENAME = "index.md";
7
+ /** Topic names reserved by the system — cannot be written by external agents. */
8
+ export declare const RESERVED_TOPICS: ReadonlySet<string>;
9
+ /** Allowed values for the topic status field in YAML frontmatter. */
10
+ export type TopicStatus = "active" | "deprecated" | "superseded";
11
+ /** Parsed YAML frontmatter from a topic file. All fields optional. */
12
+ export interface TopicFrontmatter {
13
+ description?: string;
14
+ status?: TopicStatus;
15
+ /** The topic this one replaces (used by the newer topic). */
16
+ supersedes?: string;
17
+ /** The topic that replaced this one (used by the older topic). */
18
+ superseded_by?: string;
19
+ }
20
+ /** Badge prefix used in index.md for non-active topics. */
21
+ export declare const STATUS_BADGES: ReadonlyMap<TopicStatus, string>;
22
+ /**
23
+ * Regex pattern for validating topic names.
24
+ * Allows lowercase alphanumeric with single hyphens or underscores as separators.
25
+ * Examples: "api_contracts", "auth-rules", "migration-v2"
26
+ */
4
27
  export declare const TOPIC_PATTERN: RegExp;
28
+ /**
29
+ * Custom error class for user input validation errors.
30
+ * Thrown when topic names, content, or other user inputs fail validation.
31
+ */
5
32
  export declare class UserInputError extends Error {
6
33
  constructor(message: string);
7
34
  }
35
+ /**
36
+ * Creates a standardized MCP tool response object.
37
+ * @param text - Response message
38
+ * @param isError - Whether this is an error response (default: false)
39
+ */
8
40
  export declare function textResult(text: string, isError?: boolean): {
9
41
  isError?: boolean;
10
42
  content: {
@@ -12,5 +44,13 @@ export declare function textResult(text: string, isError?: boolean): {
12
44
  text: string;
13
45
  }[];
14
46
  };
47
+ /**
48
+ * Safely extracts error message from unknown error types.
49
+ * Handles Error objects, strings, and unknown values.
50
+ */
15
51
  export declare function getErrorMessage(error: unknown): string;
52
+ /**
53
+ * Type guard to check if an error is a Node.js filesystem error.
54
+ * Useful for handling ENOENT, EACCES, etc. from fs operations.
55
+ */
16
56
  export declare function isNodeError(error: unknown): error is NodeJS.ErrnoException;
package/dist/types.js CHANGED
@@ -1,13 +1,37 @@
1
+ /** MCP server identifier used in protocol handshakes. */
1
2
  export const SERVER_NAME = "opencontext-mcp";
2
- export const SERVER_VERSION = "0.1.0";
3
- export const CONTEXT_DIRECTORY_NAME = ".opencontext";
3
+ /** Current server version for compatibility checks. */
4
+ export const SERVER_VERSION = "1.2.0";
5
+ /** Filename for the auto-generated context index. */
6
+ export const INDEX_FILENAME = "index.md";
7
+ /** Topic names reserved by the system — cannot be written by external agents. */
8
+ export const RESERVED_TOPICS = new Set(["index"]);
9
+ /** Badge prefix used in index.md for non-active topics. */
10
+ export const STATUS_BADGES = new Map([
11
+ ["deprecated", "[DEPRECATED]"],
12
+ ["superseded", "[SUPERSEDED]"],
13
+ ]);
14
+ /**
15
+ * Regex pattern for validating topic names.
16
+ * Allows lowercase alphanumeric with single hyphens or underscores as separators.
17
+ * Examples: "api_contracts", "auth-rules", "migration-v2"
18
+ */
4
19
  export const TOPIC_PATTERN = /^[a-z0-9]+(?:[_-][a-z0-9]+)*$/;
20
+ /**
21
+ * Custom error class for user input validation errors.
22
+ * Thrown when topic names, content, or other user inputs fail validation.
23
+ */
5
24
  export class UserInputError extends Error {
6
25
  constructor(message) {
7
26
  super(message);
8
27
  this.name = "UserInputError";
9
28
  }
10
29
  }
30
+ /**
31
+ * Creates a standardized MCP tool response object.
32
+ * @param text - Response message
33
+ * @param isError - Whether this is an error response (default: false)
34
+ */
11
35
  export function textResult(text, isError = false) {
12
36
  return {
13
37
  content: [
@@ -19,12 +43,20 @@ export function textResult(text, isError = false) {
19
43
  ...(isError ? { isError: true } : {}),
20
44
  };
21
45
  }
46
+ /**
47
+ * Safely extracts error message from unknown error types.
48
+ * Handles Error objects, strings, and unknown values.
49
+ */
22
50
  export function getErrorMessage(error) {
23
51
  if (error instanceof Error) {
24
52
  return error.message;
25
53
  }
26
54
  return "An unknown error occurred.";
27
55
  }
56
+ /**
57
+ * Type guard to check if an error is a Node.js filesystem error.
58
+ * Useful for handling ENOENT, EACCES, etc. from fs operations.
59
+ */
28
60
  export function isNodeError(error) {
29
61
  return error instanceof Error && "code" in error;
30
62
  }
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAC7C,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AACtC,MAAM,CAAC,MAAM,sBAAsB,GAAG,cAAc,CAAC;AACrD,MAAM,CAAC,MAAM,aAAa,GAAG,+BAA+B,CAAC;AAE7D,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAO,GAAG,KAAK;IACtD,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI;aACL;SACF;QACD,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IAED,OAAO,4BAA4B,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,CAAC;AACnD,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAE7C,uDAAuD;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC,qDAAqD;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AAEzC,iFAAiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AAevE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,aAAa,GAAqC,IAAI,GAAG,CAAC;IACrE,CAAC,YAAY,EAAE,cAAc,CAAC;IAC9B,CAAC,YAAY,EAAE,cAAc,CAAC;CAC/B,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,+BAA+B,CAAC;AAE7D;;;GAGG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAO,GAAG,KAAK;IACtD,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI;aACL;SACF;QACD,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IAED,OAAO,4BAA4B,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,CAAC;AACnD,CAAC"}
@@ -1 +1,47 @@
1
+ /**
2
+ * Options for configuring write guard behavior.
3
+ */
4
+ export interface GuardOptions {
5
+ /** Maximum payload size in KB (default: 50 KB) */
6
+ maxFileSizeKb?: number;
7
+ /** Whether to allow empty content (default: false) */
8
+ allowEmpty?: boolean;
9
+ /** Whether to check for forbidden patterns (default: true) */
10
+ strictPatternCheck?: boolean;
11
+ }
12
+ /**
13
+ * Result of write guard validation.
14
+ */
15
+ export interface GuardResult {
16
+ /** Whether the write is allowed */
17
+ allowed: boolean;
18
+ /** Human-readable reason for rejection */
19
+ reason?: string;
20
+ /** Error code for programmatic handling */
21
+ code?: "EMPTY_CONTENT" | "PAYLOAD_TOO_LARGE" | "INVALID_TOPIC" | "PATH_TRAVERSAL" | "FORBIDDEN_PATTERN" | "RESERVED_TOPIC";
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
+ */
1
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
+ /**
40
+ * Validates a write payload for context storage.
41
+ * Checks topic validity, content safety, size limits, and forbidden patterns.
42
+ * @param topic - The topic name to validate
43
+ * @param content - The content to validate
44
+ * @param options - Optional configuration for validation behavior
45
+ * @returns GuardResult indicating whether the write is allowed
46
+ */
47
+ export declare function validateWritePayload(topic: string, content: string, options?: GuardOptions): GuardResult;