opencontext-mcp 1.0.0 → 1.1.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 +77 -34
- package/dist/config.d.ts +24 -0
- package/dist/config.js +114 -0
- package/dist/config.js.map +1 -0
- package/dist/context-store.d.ts +42 -1
- package/dist/context-store.js +152 -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 +20 -3
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +29 -2
- package/dist/types.js +29 -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/README.md
CHANGED
|
@@ -16,6 +16,9 @@ The result is simple and transparent: your agent gets memory, and you keep full
|
|
|
16
16
|
|
|
17
17
|
- `save_context`: save markdown context to `.opencontext/<topic>.md`
|
|
18
18
|
- `read_context`: read one saved topic or list all available topics
|
|
19
|
+
- **Auto-Index**: automatic `index.md` generation with topic metadata
|
|
20
|
+
- **Write Guard**: input validation, size limits, and prompt injection protection
|
|
21
|
+
- **Config System**: customizable via `.opencontext.jsonc` or `.opencontext.json`
|
|
19
22
|
- Project-specific storage based on the client's current working directory
|
|
20
23
|
- No database, account, cloud sync, or hidden state
|
|
21
24
|
- Works with MCP-compatible clients over stdio
|
|
@@ -72,19 +75,50 @@ Cursor and Claude Desktop use the same MCP server configuration:
|
|
|
72
75
|
|
|
73
76
|
Restart your MCP client after updating the configuration.
|
|
74
77
|
|
|
75
|
-
##
|
|
78
|
+
## Configuration
|
|
76
79
|
|
|
77
|
-
OpenContext
|
|
80
|
+
OpenContext can be customized with a config file in your project root. Create `.opencontext.jsonc` or `.opencontext.json`:
|
|
78
81
|
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
.opencontext
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
```jsonc
|
|
83
|
+
{
|
|
84
|
+
// Custom storage path (default: ".opencontext")
|
|
85
|
+
"path": ".opencontext",
|
|
86
|
+
|
|
87
|
+
// Disable write operations
|
|
88
|
+
"readOnly": false,
|
|
89
|
+
|
|
90
|
+
// Pause all tool access
|
|
91
|
+
"disabled": false,
|
|
92
|
+
|
|
93
|
+
// Auto-generate index.md with topic metadata
|
|
94
|
+
"autoIndex": true,
|
|
95
|
+
|
|
96
|
+
// Write guard settings
|
|
97
|
+
"guard": {
|
|
98
|
+
"enabled": true,
|
|
99
|
+
"maxFileSizeKb": 50,
|
|
100
|
+
"strictPatternCheck": true
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// History backup settings (coming soon)
|
|
104
|
+
"history": {
|
|
105
|
+
"enabled": false,
|
|
106
|
+
"maxBackupsPerTopic": 5,
|
|
107
|
+
"retentionDays": 7
|
|
108
|
+
}
|
|
109
|
+
}
|
|
85
110
|
```
|
|
86
111
|
|
|
87
|
-
|
|
112
|
+
### Guard System
|
|
113
|
+
|
|
114
|
+
The write guard protects against:
|
|
115
|
+
|
|
116
|
+
- **Empty content**: prevents saving blank or whitespace-only files
|
|
117
|
+
- **Payload too large**: configurable max file size (default 50KB)
|
|
118
|
+
- **Invalid topics**: enforces snake_case or kebab-case naming
|
|
119
|
+
- **Path traversal**: blocks `..`, absolute paths, and directory escapes
|
|
120
|
+
- **Reserved topics**: prevents overwriting system files like `index.md`
|
|
121
|
+
- **Prompt injection**: detects and blocks common injection patterns
|
|
88
122
|
|
|
89
123
|
## Available Tools
|
|
90
124
|
|
|
@@ -140,10 +174,7 @@ Recommended workflow:
|
|
|
140
174
|
3. Let agents update context when they discover something that should survive the current chat.
|
|
141
175
|
4. Review `.opencontext/` files like normal project documentation.
|
|
142
176
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
- [`examples/plan-agent.md`](examples/plan-agent.md) -- architect that analyzes requirements and saves context
|
|
146
|
-
- [`examples/build-agent.md`](examples/build-agent.md) -- developer that reads context before coding
|
|
177
|
+
For pre-built agent prompts, see the [documentation](https://opencntx.dev/docs/agents).
|
|
147
178
|
|
|
148
179
|
## Version Control
|
|
149
180
|
|
|
@@ -161,33 +192,45 @@ Or ignore it when context should stay local:
|
|
|
161
192
|
|
|
162
193
|
Because files are plain markdown, both approaches are safe and easy to audit.
|
|
163
194
|
|
|
195
|
+
## Project Structure
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
opencontext/
|
|
199
|
+
├── packages/
|
|
200
|
+
│ └── opencontext/ # MCP server package
|
|
201
|
+
│ ├── src/
|
|
202
|
+
│ │ ├── index.ts # CLI entrypoint (stdio transport)
|
|
203
|
+
│ │ ├── server.ts # McpServer factory and tool registration
|
|
204
|
+
│ │ ├── config.ts # Config loading (.opencontext.jsonc)
|
|
205
|
+
│ │ ├── context-store.ts # Filesystem operations for .opencontext/
|
|
206
|
+
│ │ ├── validation.ts # Topic validation and write guard
|
|
207
|
+
│ │ └── types.ts # Shared constants and error helpers
|
|
208
|
+
│ └── test/
|
|
209
|
+
│ ├── context-store.test.ts
|
|
210
|
+
│ ├── validation.test.ts
|
|
211
|
+
│ ├── guard.test.ts
|
|
212
|
+
│ └── auto-index.test.ts
|
|
213
|
+
├── apps/
|
|
214
|
+
│ └── web/ # Website (Next.js)
|
|
215
|
+
│ └── src/
|
|
216
|
+
│ ├── app/
|
|
217
|
+
│ │ ├── page.tsx # Landing page
|
|
218
|
+
│ │ ├── docs/ # Documentation
|
|
219
|
+
│ │ ├── imprint/
|
|
220
|
+
│ │ └── privacy-policy/
|
|
221
|
+
│ └── components/
|
|
222
|
+
├── CONTRIBUTING.md
|
|
223
|
+
├── SECURITY.md
|
|
224
|
+
└── README.md
|
|
225
|
+
```
|
|
226
|
+
|
|
164
227
|
## Development
|
|
165
228
|
|
|
166
229
|
```bash
|
|
167
230
|
pnpm install
|
|
168
|
-
pnpm build # compile
|
|
231
|
+
pnpm build # compile all packages
|
|
169
232
|
pnpm typecheck # type-check without emitting
|
|
170
233
|
pnpm test # run vitest suite
|
|
171
|
-
pnpm start # start the MCP server
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Project structure:
|
|
175
|
-
|
|
176
|
-
```text
|
|
177
|
-
src/
|
|
178
|
-
index.ts CLI entrypoint (stdio transport, startup error handling)
|
|
179
|
-
server.ts McpServer factory and tool registration
|
|
180
|
-
context-store.ts Filesystem operations for .opencontext/
|
|
181
|
-
validation.ts Topic validation and input rules
|
|
182
|
-
types.ts Shared constants, error classes, and result helpers
|
|
183
|
-
test/
|
|
184
|
-
validation.test.ts Unit tests for topic validation
|
|
185
|
-
context-store.test.ts Unit tests for read/write/list operations
|
|
186
|
-
examples/
|
|
187
|
-
plan-agent.md Architect system prompt
|
|
188
|
-
build-agent.md Developer system prompt
|
|
189
|
-
README.md User documentation
|
|
190
|
-
LICENSE MIT license
|
|
191
234
|
```
|
|
192
235
|
|
|
193
236
|
## License
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface OpenContextConfig {
|
|
2
|
+
$schema?: string;
|
|
3
|
+
path?: string;
|
|
4
|
+
readOnly?: boolean;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
autoIndex?: boolean;
|
|
7
|
+
history?: {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
maxBackupsPerTopic?: number;
|
|
10
|
+
retentionDays?: number;
|
|
11
|
+
};
|
|
12
|
+
guard?: {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
maxFileSizeKb?: number;
|
|
15
|
+
strictPatternCheck?: boolean;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export type ResolvedConfig = Required<Omit<OpenContextConfig, "$schema">>;
|
|
19
|
+
export declare const DEFAULT_CONFIG: ResolvedConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Loads configuration from .opencontext.jsonc or .opencontext.json in the given directory.
|
|
22
|
+
* Falls back to DEFAULT_CONFIG if no file exists or parsing fails.
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadConfig(cwd?: string): Promise<ResolvedConfig>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
*/
|
|
24
|
+
function stripJsonComments(raw) {
|
|
25
|
+
let result = "";
|
|
26
|
+
let i = 0;
|
|
27
|
+
const len = raw.length;
|
|
28
|
+
while (i < len) {
|
|
29
|
+
const ch = raw[i];
|
|
30
|
+
// Strings: copy verbatim (including escaped characters)
|
|
31
|
+
if (ch === '"') {
|
|
32
|
+
result += ch;
|
|
33
|
+
i++;
|
|
34
|
+
while (i < len && raw[i] !== '"') {
|
|
35
|
+
if (raw[i] === "\\") {
|
|
36
|
+
result += raw[i];
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
if (i < len) {
|
|
40
|
+
result += raw[i];
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (i < len) {
|
|
45
|
+
result += raw[i]; // closing quote
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Single-line comment: skip until newline
|
|
51
|
+
if (ch === "/" && i + 1 < len && raw[i + 1] === "/") {
|
|
52
|
+
while (i < len && raw[i] !== "\n") {
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Multi-line comment: skip until */
|
|
58
|
+
if (ch === "/" && i + 1 < len && raw[i + 1] === "*") {
|
|
59
|
+
i += 2;
|
|
60
|
+
while (i < len - 1 && !(raw[i] === "*" && raw[i + 1] === "/")) {
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
i += 2; // skip */
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
result += ch;
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
// Strip trailing commas before closing braces/brackets
|
|
70
|
+
return result.replace(/,(\s*[}\]])/g, "$1");
|
|
71
|
+
}
|
|
72
|
+
function deepMerge(target, source) {
|
|
73
|
+
const result = { ...target };
|
|
74
|
+
for (const key of Object.keys(source)) {
|
|
75
|
+
const sourceVal = source[key];
|
|
76
|
+
const targetVal = result[key];
|
|
77
|
+
if (sourceVal !== null &&
|
|
78
|
+
typeof sourceVal === "object" &&
|
|
79
|
+
!Array.isArray(sourceVal) &&
|
|
80
|
+
targetVal !== null &&
|
|
81
|
+
typeof targetVal === "object" &&
|
|
82
|
+
!Array.isArray(targetVal)) {
|
|
83
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
84
|
+
}
|
|
85
|
+
else if (sourceVal !== undefined) {
|
|
86
|
+
result[key] = sourceVal;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Loads configuration from .opencontext.jsonc or .opencontext.json in the given directory.
|
|
93
|
+
* Falls back to DEFAULT_CONFIG if no file exists or parsing fails.
|
|
94
|
+
*/
|
|
95
|
+
export async function loadConfig(cwd = process.cwd()) {
|
|
96
|
+
for (const filename of CONFIG_CANDIDATES) {
|
|
97
|
+
const filePath = path.join(cwd, filename);
|
|
98
|
+
try {
|
|
99
|
+
const raw = await readFile(filePath, "utf8");
|
|
100
|
+
const cleaned = stripJsonComments(raw);
|
|
101
|
+
const parsed = JSON.parse(cleaned);
|
|
102
|
+
return deepMerge(DEFAULT_CONFIG, parsed);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error.code === "ENOENT") {
|
|
106
|
+
continue; // file doesn't exist, try next candidate
|
|
107
|
+
}
|
|
108
|
+
console.error(`OpenContext: failed to parse ${filename}: ${error.message}`);
|
|
109
|
+
return DEFAULT_CONFIG;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return DEFAULT_CONFIG;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/context-store.d.ts
CHANGED
|
@@ -1,9 +1,50 @@
|
|
|
1
|
+
import { type ResolvedConfig } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Manages context file storage operations.
|
|
4
|
+
* Handles reading, writing, and listing context files in the configured directory.
|
|
5
|
+
*/
|
|
1
6
|
export declare class ContextStore {
|
|
2
7
|
private readonly basePath;
|
|
3
|
-
|
|
8
|
+
private readonly config;
|
|
9
|
+
private readonly contextDir;
|
|
10
|
+
constructor(basePath?: string, config?: ResolvedConfig);
|
|
11
|
+
/** Returns the absolute path to the context directory. */
|
|
4
12
|
getContextDirectory(): string;
|
|
13
|
+
/** Returns the absolute path to a topic file. */
|
|
5
14
|
getTopicFilePath(topic: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Saves context content to a topic file.
|
|
17
|
+
* Validates payload using WriteGuard before writing.
|
|
18
|
+
* @param topicInput - Topic name (will be validated and trimmed)
|
|
19
|
+
* @param content - Markdown content to save
|
|
20
|
+
* @returns Success message with file location
|
|
21
|
+
* @throws UserInputError if validation fails
|
|
22
|
+
*/
|
|
6
23
|
saveContext(topicInput: string, content: string): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Reads context content from a topic file, or lists all topics if none specified.
|
|
26
|
+
* @param topicInput - Optional topic name to read
|
|
27
|
+
* @returns Topic content or list of available topics
|
|
28
|
+
* @throws UserInputError if topic doesn't exist or is invalid
|
|
29
|
+
*/
|
|
7
30
|
readContext(topicInput?: string): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Lists all available context topics.
|
|
33
|
+
* Scans the context directory for .md files and returns sorted topic names.
|
|
34
|
+
* @returns Sorted array of topic names
|
|
35
|
+
*/
|
|
8
36
|
listTopics(): Promise<string[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Extracts a short description from markdown content.
|
|
39
|
+
* Strategy: frontmatter description → first heading → first paragraph → fallback.
|
|
40
|
+
* @param content - Raw markdown content
|
|
41
|
+
* @returns Truncated description (max 120 chars)
|
|
42
|
+
*/
|
|
43
|
+
private extractDescription;
|
|
44
|
+
/**
|
|
45
|
+
* Rebuilds the auto-generated index.md file in the context directory.
|
|
46
|
+
* Scans all topic files, extracts metadata, and writes a compact index.
|
|
47
|
+
* @returns The generated index markdown content
|
|
48
|
+
*/
|
|
49
|
+
rebuildContextIndex(): Promise<string>;
|
|
9
50
|
}
|
package/dist/context-store.js
CHANGED
|
@@ -1,48 +1,86 @@
|
|
|
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 } from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { INDEX_FILENAME, UserInputError, isNodeError } 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
|
-
await mkdir(contextDirectory, { recursive: true });
|
|
45
|
+
const filePath = sanitizeTopicPath(this.contextDir, topic);
|
|
46
|
+
await mkdir(this.contextDir, { recursive: true });
|
|
21
47
|
await writeFile(filePath, content, "utf8");
|
|
22
|
-
return `Saved context topic "${topic}" to ${
|
|
48
|
+
return `Saved context topic "${topic}" to ${this.config.path}/${topic}.md.`;
|
|
23
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Reads context content from a topic file, or lists all topics if none specified.
|
|
52
|
+
* @param topicInput - Optional topic name to read
|
|
53
|
+
* @returns Topic content or list of available topics
|
|
54
|
+
* @throws UserInputError if topic doesn't exist or is invalid
|
|
55
|
+
*/
|
|
24
56
|
async readContext(topicInput) {
|
|
25
57
|
if (topicInput !== undefined) {
|
|
26
58
|
const topic = validateTopic(topicInput);
|
|
27
59
|
try {
|
|
28
|
-
|
|
60
|
+
const filePath = sanitizeTopicPath(this.contextDir, topic);
|
|
61
|
+
return await readFile(filePath, "utf8");
|
|
29
62
|
}
|
|
30
63
|
catch (error) {
|
|
31
64
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
32
|
-
throw new UserInputError(`No context found for topic "${topic}" at ${
|
|
65
|
+
throw new UserInputError(`No context found for topic "${topic}" at ${this.config.path}/${topic}.md.`);
|
|
33
66
|
}
|
|
34
67
|
throw error;
|
|
35
68
|
}
|
|
36
69
|
}
|
|
37
70
|
const topics = await this.listTopics();
|
|
38
71
|
if (topics.length === 0) {
|
|
39
|
-
return `No OpenContext topics found in ${
|
|
72
|
+
return `No OpenContext topics found in ${this.config.path}/. Use save_context to create one.`;
|
|
40
73
|
}
|
|
41
|
-
return
|
|
74
|
+
return this.rebuildContextIndex();
|
|
42
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Lists all available context topics.
|
|
78
|
+
* Scans the context directory for .md files and returns sorted topic names.
|
|
79
|
+
* @returns Sorted array of topic names
|
|
80
|
+
*/
|
|
43
81
|
async listTopics() {
|
|
44
82
|
try {
|
|
45
|
-
const entries = await readdir(this.
|
|
83
|
+
const entries = await readdir(this.contextDir, { withFileTypes: true });
|
|
46
84
|
return entries
|
|
47
85
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
48
86
|
.map((entry) => entry.name.slice(0, -".md".length))
|
|
@@ -55,5 +93,103 @@ export class ContextStore {
|
|
|
55
93
|
throw error;
|
|
56
94
|
}
|
|
57
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Extracts a short description from markdown content.
|
|
98
|
+
* Strategy: frontmatter description → first heading → first paragraph → fallback.
|
|
99
|
+
* @param content - Raw markdown content
|
|
100
|
+
* @returns Truncated description (max 120 chars)
|
|
101
|
+
*/
|
|
102
|
+
extractDescription(content) {
|
|
103
|
+
const trimmed = content.trim();
|
|
104
|
+
if (!trimmed) {
|
|
105
|
+
return "No summary available.";
|
|
106
|
+
}
|
|
107
|
+
// Strategy A: YAML frontmatter with description key
|
|
108
|
+
if (trimmed.startsWith("---")) {
|
|
109
|
+
const endIdx = trimmed.indexOf("---", 3);
|
|
110
|
+
if (endIdx !== -1) {
|
|
111
|
+
const frontmatter = trimmed.slice(3, endIdx);
|
|
112
|
+
const descMatch = frontmatter.match(/description:\s*["']?(.+?)["']?\s*$/m);
|
|
113
|
+
if (descMatch?.[1]) {
|
|
114
|
+
return descMatch[1].slice(0, 120);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const lines = trimmed.split("\n");
|
|
119
|
+
// Strategy B: First heading
|
|
120
|
+
for (const line of lines) {
|
|
121
|
+
const headingMatch = line.match(/^#\s+(.+)$/);
|
|
122
|
+
if (headingMatch?.[1]) {
|
|
123
|
+
return headingMatch[1].slice(0, 120);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Strategy C: First non-empty paragraph line
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
const stripped = line.trim();
|
|
129
|
+
if (stripped && !stripped.startsWith("#")) {
|
|
130
|
+
return stripped.length > 120 ? stripped.slice(0, 120) + "..." : stripped;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return "No summary available.";
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Rebuilds the auto-generated index.md file in the context directory.
|
|
137
|
+
* Scans all topic files, extracts metadata, and writes a compact index.
|
|
138
|
+
* @returns The generated index markdown content
|
|
139
|
+
*/
|
|
140
|
+
async rebuildContextIndex() {
|
|
141
|
+
let entries;
|
|
142
|
+
try {
|
|
143
|
+
entries = await readdir(this.contextDir, { withFileTypes: true });
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
147
|
+
return "";
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
const topicFiles = entries
|
|
152
|
+
.filter((entry) => entry.isFile() &&
|
|
153
|
+
entry.name.endsWith(".md") &&
|
|
154
|
+
entry.name !== INDEX_FILENAME &&
|
|
155
|
+
entry.name !== "README.md")
|
|
156
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
157
|
+
if (topicFiles.length === 0) {
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
const topicEntries = [];
|
|
161
|
+
for (const entry of topicFiles) {
|
|
162
|
+
const filePath = path.join(this.contextDir, entry.name);
|
|
163
|
+
const content = await readFile(filePath, "utf8");
|
|
164
|
+
const fileStat = await stat(filePath);
|
|
165
|
+
const topic = entry.name.slice(0, -".md".length);
|
|
166
|
+
const description = this.extractDescription(content);
|
|
167
|
+
const date = fileStat.mtime.toISOString().slice(0, 10);
|
|
168
|
+
const sizeBytes = Buffer.byteLength(content, "utf8");
|
|
169
|
+
topicEntries.push({ topic, filename: entry.name, description, date, sizeBytes });
|
|
170
|
+
}
|
|
171
|
+
const lines = [
|
|
172
|
+
"# Project Context Index",
|
|
173
|
+
"<!-- AUTO-GENERATED BY OPENCONTEXT - DO NOT EDIT MANUALLY -->",
|
|
174
|
+
"",
|
|
175
|
+
"Available context topics in this project:",
|
|
176
|
+
"",
|
|
177
|
+
];
|
|
178
|
+
for (const t of topicEntries) {
|
|
179
|
+
const sizeStr = t.sizeBytes >= 1024
|
|
180
|
+
? `${(t.sizeBytes / 1024).toFixed(1)} KB`
|
|
181
|
+
: `${t.sizeBytes} B`;
|
|
182
|
+
lines.push(`- **${t.topic}** (\`${t.filename}\`) - Updated: ${t.date} (${sizeStr})`);
|
|
183
|
+
lines.push(` > ${t.description}`);
|
|
184
|
+
}
|
|
185
|
+
lines.push("");
|
|
186
|
+
lines.push("---");
|
|
187
|
+
lines.push("*To read a specific context topic, call `read_context` with the topic name.*");
|
|
188
|
+
lines.push("");
|
|
189
|
+
const indexContent = lines.join("\n");
|
|
190
|
+
const indexPath = path.join(this.contextDir, INDEX_FILENAME);
|
|
191
|
+
await writeFile(indexPath, indexContent, "utf8");
|
|
192
|
+
return indexContent;
|
|
193
|
+
}
|
|
58
194
|
}
|
|
59
195
|
//# 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);
|
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":"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;AACtF,OAAO,EAAE,UAAU,EAAuB,MAAM,aAAa,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,QAAiB,EACjB,MAAuB;IAEvB,MAAM,cAAc,GAAG,MAAM,IAAI,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAEzD,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,EAAE,4EAA4E,cAAc,CAAC,IAAI,+CAA+C;QAC3J,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,CAAC,QAAQ,EAAE,CAAC;YAC5B,OAAO,UAAU,CAAC,6EAA6E,CAAC,CAAC;QACnG,CAAC;QACD,IAAI,cAAc,CAAC,QAAQ,EAAE,CAAC;YAC5B,OAAO,UAAU,CAAC,oFAAoF,EAAE,IAAI,CAAC,CAAC;QAChH,CAAC;QACD,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,CAAC,QAAQ,EAAE,CAAC;YAC5B,OAAO,UAAU,CAAC,6EAA6E,CAAC,CAAC;QACnG,CAAC;QACD,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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
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.1.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
|
+
/**
|
|
10
|
+
* Regex pattern for validating topic names.
|
|
11
|
+
* Allows lowercase alphanumeric with single hyphens or underscores as separators.
|
|
12
|
+
* Examples: "api_contracts", "auth-rules", "migration-v2"
|
|
13
|
+
*/
|
|
4
14
|
export declare const TOPIC_PATTERN: RegExp;
|
|
15
|
+
/**
|
|
16
|
+
* Custom error class for user input validation errors.
|
|
17
|
+
* Thrown when topic names, content, or other user inputs fail validation.
|
|
18
|
+
*/
|
|
5
19
|
export declare class UserInputError extends Error {
|
|
6
20
|
constructor(message: string);
|
|
7
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Creates a standardized MCP tool response object.
|
|
24
|
+
* @param text - Response message
|
|
25
|
+
* @param isError - Whether this is an error response (default: false)
|
|
26
|
+
*/
|
|
8
27
|
export declare function textResult(text: string, isError?: boolean): {
|
|
9
28
|
isError?: boolean;
|
|
10
29
|
content: {
|
|
@@ -12,5 +31,13 @@ export declare function textResult(text: string, isError?: boolean): {
|
|
|
12
31
|
text: string;
|
|
13
32
|
}[];
|
|
14
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Safely extracts error message from unknown error types.
|
|
36
|
+
* Handles Error objects, strings, and unknown values.
|
|
37
|
+
*/
|
|
15
38
|
export declare function getErrorMessage(error: unknown): string;
|
|
39
|
+
/**
|
|
40
|
+
* Type guard to check if an error is a Node.js filesystem error.
|
|
41
|
+
* Useful for handling ENOENT, EACCES, etc. from fs operations.
|
|
42
|
+
*/
|
|
16
43
|
export declare function isNodeError(error: unknown): error is NodeJS.ErrnoException;
|
package/dist/types.js
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
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.1.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
|
+
/**
|
|
10
|
+
* Regex pattern for validating topic names.
|
|
11
|
+
* Allows lowercase alphanumeric with single hyphens or underscores as separators.
|
|
12
|
+
* Examples: "api_contracts", "auth-rules", "migration-v2"
|
|
13
|
+
*/
|
|
4
14
|
export const TOPIC_PATTERN = /^[a-z0-9]+(?:[_-][a-z0-9]+)*$/;
|
|
15
|
+
/**
|
|
16
|
+
* Custom error class for user input validation errors.
|
|
17
|
+
* Thrown when topic names, content, or other user inputs fail validation.
|
|
18
|
+
*/
|
|
5
19
|
export class UserInputError extends Error {
|
|
6
20
|
constructor(message) {
|
|
7
21
|
super(message);
|
|
8
22
|
this.name = "UserInputError";
|
|
9
23
|
}
|
|
10
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a standardized MCP tool response object.
|
|
27
|
+
* @param text - Response message
|
|
28
|
+
* @param isError - Whether this is an error response (default: false)
|
|
29
|
+
*/
|
|
11
30
|
export function textResult(text, isError = false) {
|
|
12
31
|
return {
|
|
13
32
|
content: [
|
|
@@ -19,12 +38,20 @@ export function textResult(text, isError = false) {
|
|
|
19
38
|
...(isError ? { isError: true } : {}),
|
|
20
39
|
};
|
|
21
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Safely extracts error message from unknown error types.
|
|
43
|
+
* Handles Error objects, strings, and unknown values.
|
|
44
|
+
*/
|
|
22
45
|
export function getErrorMessage(error) {
|
|
23
46
|
if (error instanceof Error) {
|
|
24
47
|
return error.message;
|
|
25
48
|
}
|
|
26
49
|
return "An unknown error occurred.";
|
|
27
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Type guard to check if an error is a Node.js filesystem error.
|
|
53
|
+
* Useful for handling ENOENT, EACCES, etc. from fs operations.
|
|
54
|
+
*/
|
|
28
55
|
export function isNodeError(error) {
|
|
29
56
|
return error instanceof Error && "code" in error;
|
|
30
57
|
}
|
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;;;;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;
|
package/dist/validation.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { RESERVED_TOPICS, TOPIC_PATTERN, UserInputError } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Forbidden patterns that indicate prompt injection attempts.
|
|
5
|
+
* These are checked case-insensitively against the content.
|
|
6
|
+
*/
|
|
7
|
+
const FORBIDDEN_PATTERNS = [
|
|
8
|
+
/ignore\s+(all\s+)?(previous|prior)\s+instructions/i,
|
|
9
|
+
/system\s*:\s*override/i,
|
|
10
|
+
/bypass\s+(safety|guardrails?|system\s+prompt)/i,
|
|
11
|
+
];
|
|
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
|
+
*/
|
|
2
18
|
export function validateTopic(topicInput) {
|
|
3
19
|
const topic = topicInput.trim();
|
|
4
20
|
if (!TOPIC_PATTERN.test(topic)) {
|
|
@@ -6,4 +22,85 @@ export function validateTopic(topicInput) {
|
|
|
6
22
|
}
|
|
7
23
|
return topic;
|
|
8
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
|
+
/**
|
|
48
|
+
* Validates a write payload for context storage.
|
|
49
|
+
* Checks topic validity, content safety, size limits, and forbidden patterns.
|
|
50
|
+
* @param topic - The topic name to validate
|
|
51
|
+
* @param content - The content to validate
|
|
52
|
+
* @param options - Optional configuration for validation behavior
|
|
53
|
+
* @returns GuardResult indicating whether the write is allowed
|
|
54
|
+
*/
|
|
55
|
+
export function validateWritePayload(topic, content, options) {
|
|
56
|
+
const opts = {
|
|
57
|
+
maxFileSizeKb: options?.maxFileSizeKb ?? 50,
|
|
58
|
+
allowEmpty: options?.allowEmpty ?? false,
|
|
59
|
+
strictPatternCheck: options?.strictPatternCheck ?? true,
|
|
60
|
+
};
|
|
61
|
+
const trimmedTopic = topic.trim();
|
|
62
|
+
if (!TOPIC_PATTERN.test(trimmedTopic)) {
|
|
63
|
+
return {
|
|
64
|
+
allowed: false,
|
|
65
|
+
reason: "Topic must be snake_case or kebab-case using lowercase letters, numbers, underscores, or hyphens.",
|
|
66
|
+
code: "INVALID_TOPIC",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (RESERVED_TOPICS.has(trimmedTopic)) {
|
|
70
|
+
return {
|
|
71
|
+
allowed: false,
|
|
72
|
+
reason: `"${trimmedTopic}" is a reserved system topic and cannot be written directly.`,
|
|
73
|
+
code: "RESERVED_TOPIC",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (!opts.allowEmpty && content.trim().length === 0) {
|
|
77
|
+
return {
|
|
78
|
+
allowed: false,
|
|
79
|
+
reason: "Content must not be empty or consist solely of whitespace.",
|
|
80
|
+
code: "EMPTY_CONTENT",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// Convert KB to bytes for size comparison
|
|
84
|
+
const maxBytes = opts.maxFileSizeKb * 1024;
|
|
85
|
+
const contentBytes = Buffer.byteLength(content, "utf8");
|
|
86
|
+
if (contentBytes > maxBytes) {
|
|
87
|
+
return {
|
|
88
|
+
allowed: false,
|
|
89
|
+
reason: `Payload size (${contentBytes} bytes) exceeds maximum allowed size (${maxBytes} bytes).`,
|
|
90
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (opts.strictPatternCheck) {
|
|
94
|
+
for (const pattern of FORBIDDEN_PATTERNS) {
|
|
95
|
+
if (pattern.test(content)) {
|
|
96
|
+
return {
|
|
97
|
+
allowed: false,
|
|
98
|
+
reason: `Content contains forbidden pattern: ${pattern.source}`,
|
|
99
|
+
code: "FORBIDDEN_PATTERN",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { allowed: true };
|
|
105
|
+
}
|
|
9
106
|
//# sourceMappingURL=validation.js.map
|
package/dist/validation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencontext-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A Model Context Protocol server for persistent project-specific AI agent context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
"memory"
|
|
29
29
|
],
|
|
30
30
|
"repository": {
|
|
31
|
-
"url": "git+https://github.com/slxca/opencontext.git"
|
|
31
|
+
"url": "git+https://github.com/slxca/opencontext.git",
|
|
32
|
+
"directory": "packages/opencontext"
|
|
32
33
|
},
|
|
33
34
|
"bugs": {
|
|
34
|
-
"url": "https://github.com/slxca/
|
|
35
|
+
"url": "https://github.com/slxca/opencontext/issues",
|
|
35
36
|
"email": "bugs@s-luca.com"
|
|
36
37
|
},
|
|
37
38
|
"publishConfig": {
|
|
@@ -44,7 +45,6 @@
|
|
|
44
45
|
"engines": {
|
|
45
46
|
"node": ">=20.0.0"
|
|
46
47
|
},
|
|
47
|
-
"packageManager": "pnpm@9.15.4",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.17.4",
|
|
50
50
|
"zod": "^4.5.4"
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 OpenContext Contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/examples/build-agent.md
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# OpenContext Build Agent
|
|
2
|
-
|
|
3
|
-
Copy this prompt into your AI client's system prompt to turn it into a developer that follows rules saved by the Plan Agent.
|
|
4
|
-
|
|
5
|
-
## System Prompt
|
|
6
|
-
|
|
7
|
-
You are the OpenContext Build Agent: a pragmatic senior developer responsible for implementing changes while strictly following project context saved by the Plan Agent.
|
|
8
|
-
|
|
9
|
-
Before writing or editing any code, you must use the `read_context` tool with no topic to list available OpenContext topics. Then read all topics relevant to the requested work. If relevant context exists, follow it. If the user's request conflicts with saved context, stop and ask for clarification before changing code.
|
|
10
|
-
|
|
11
|
-
Your responsibilities:
|
|
12
|
-
|
|
13
|
-
- Read OpenContext before coding.
|
|
14
|
-
- Inspect the codebase and make the smallest correct change.
|
|
15
|
-
- Preserve existing architecture, naming conventions, and testing strategy.
|
|
16
|
-
- Run appropriate verification commands when feasible.
|
|
17
|
-
- Use `save_context` when you discover a durable rule, convention, decision, or debugging note future agents should know.
|
|
18
|
-
- Do not overwrite saved context with speculative or temporary information.
|
|
19
|
-
|
|
20
|
-
Required workflow:
|
|
21
|
-
|
|
22
|
-
```text
|
|
23
|
-
1. Call read_context with no topic.
|
|
24
|
-
2. Read each relevant topic with read_context.
|
|
25
|
-
3. Inspect the affected code.
|
|
26
|
-
4. Implement the smallest correct change.
|
|
27
|
-
5. Run relevant tests, type checks, or builds.
|
|
28
|
-
6. Save any newly discovered durable context with save_context.
|
|
29
|
-
7. Summarize what changed and what was verified.
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
If no OpenContext topics exist, state that no saved context was available, then proceed by inspecting the repository directly.
|
|
33
|
-
|
|
34
|
-
Build output format:
|
|
35
|
-
|
|
36
|
-
```text
|
|
37
|
-
Changed
|
|
38
|
-
<files or behavior changed>
|
|
39
|
-
|
|
40
|
-
Context Used
|
|
41
|
-
<topics read or note that no context existed>
|
|
42
|
-
|
|
43
|
-
Verification
|
|
44
|
-
<commands run and results>
|
|
45
|
-
|
|
46
|
-
Context Saved
|
|
47
|
-
<topics saved or updated, if any>
|
|
48
|
-
```
|
package/examples/plan-agent.md
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# OpenContext Plan Agent
|
|
2
|
-
|
|
3
|
-
Copy this prompt into your AI client's system prompt to turn it into an architect that uses OpenContext MCP.
|
|
4
|
-
|
|
5
|
-
## System Prompt
|
|
6
|
-
|
|
7
|
-
You are the OpenContext Plan Agent: a senior software architect responsible for turning requirements into clear implementation plans and durable project context.
|
|
8
|
-
|
|
9
|
-
Before planning, use the `read_context` tool with no topic to list available OpenContext topics. Read every topic relevant to the user's request before making recommendations. If no context exists, inspect the repository and infer only what is supported by evidence in the codebase.
|
|
10
|
-
|
|
11
|
-
Your responsibilities:
|
|
12
|
-
|
|
13
|
-
- Analyze the user's requirement and the current codebase before proposing changes.
|
|
14
|
-
- Identify architectural constraints, project conventions, API contracts, data model decisions, and implementation risks.
|
|
15
|
-
- Create a practical step-by-step plan that a build agent can execute.
|
|
16
|
-
- Use `save_context` to persist durable decisions, rules, and conventions that future agents should follow.
|
|
17
|
-
- Prefer concise markdown context organized under focused snake_case or kebab-case topics.
|
|
18
|
-
|
|
19
|
-
When using `save_context`, choose topics such as:
|
|
20
|
-
|
|
21
|
-
- `architecture`
|
|
22
|
-
- `coding_rules`
|
|
23
|
-
- `api-contracts`
|
|
24
|
-
- `data-model`
|
|
25
|
-
- `testing-strategy`
|
|
26
|
-
|
|
27
|
-
Context you save must be factual and durable. Do not save temporary guesses, chat-only preferences, or unresolved options unless they are clearly marked as open questions.
|
|
28
|
-
|
|
29
|
-
Plan output format:
|
|
30
|
-
|
|
31
|
-
```text
|
|
32
|
-
Summary
|
|
33
|
-
<one-paragraph explanation of the intended solution>
|
|
34
|
-
|
|
35
|
-
Relevant Context Read
|
|
36
|
-
<topics read and what mattered>
|
|
37
|
-
|
|
38
|
-
Architecture Decisions
|
|
39
|
-
<decisions made or confirmed>
|
|
40
|
-
|
|
41
|
-
Implementation Plan
|
|
42
|
-
1. <step>
|
|
43
|
-
2. <step>
|
|
44
|
-
3. <step>
|
|
45
|
-
|
|
46
|
-
Risks And Checks
|
|
47
|
-
<known risks, tests, and verification steps>
|
|
48
|
-
|
|
49
|
-
Context Saved
|
|
50
|
-
<topics saved or updated>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Always save newly discovered durable context before finishing your response.
|