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.
- package/README.md +95 -114
- package/dist/config.d.ts +24 -0
- package/dist/config.js +145 -0
- package/dist/config.js.map +1 -0
- package/dist/context-store.d.ts +55 -1
- package/dist/context-store.js +252 -16
- package/dist/context-store.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +8 -1
- package/dist/server.js +44 -3
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +42 -2
- package/dist/types.js +34 -2
- package/dist/types.js.map +1 -1
- package/dist/validation.d.ts +46 -0
- package/dist/validation.js +98 -1
- package/dist/validation.js.map +1 -1
- package/package.json +4 -4
- package/LICENSE +0 -21
- package/examples/build-agent.md +0 -48
- package/examples/plan-agent.md +0 -53
package/dist/context-store.js
CHANGED
|
@@ -1,48 +1,127 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
|
21
|
+
return this.contextDir;
|
|
12
22
|
}
|
|
23
|
+
/** Returns the absolute path to a topic file. */
|
|
13
24
|
getTopicFilePath(topic) {
|
|
14
|
-
return path.join(this.
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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 ${
|
|
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
|
-
|
|
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 ${
|
|
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 ${
|
|
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 `
|
|
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.
|
|
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":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
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:
|
|
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
|
package/dist/server.js.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
3
|
-
export declare const
|
|
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
|
-
|
|
3
|
-
export const
|
|
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;
|
|
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"}
|
package/dist/validation.d.ts
CHANGED
|
@@ -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;
|