@contextstream/mcp-server 0.4.17 → 0.4.20
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/dist/index.js +382 -40
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7504,7 +7504,7 @@ var CONTEXTSTREAM_TOOL_NAMES = [
|
|
|
7504
7504
|
];
|
|
7505
7505
|
function applyMcpToolPrefix(markdown, toolPrefix) {
|
|
7506
7506
|
const toolPattern = CONTEXTSTREAM_TOOL_NAMES.join("|");
|
|
7507
|
-
const toolRegex = new RegExp(`(?<!__)\\b(${toolPattern})\\b`, "g");
|
|
7507
|
+
const toolRegex = new RegExp(`(?<!__)\\b(${toolPattern})\\b(?=\\s*\\()`, "g");
|
|
7508
7508
|
return markdown.replace(toolRegex, `${toolPrefix}$1`);
|
|
7509
7509
|
}
|
|
7510
7510
|
var CONTEXTSTREAM_RULES_FULL = `
|
|
@@ -7527,7 +7527,9 @@ Rules Version: ${RULES_VERSION}
|
|
|
7527
7527
|
|
|
7528
7528
|
**NO EXCEPTIONS.** Do not skip even if you think you have enough context.
|
|
7529
7529
|
|
|
7530
|
-
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable, omit \`mode\` and
|
|
7530
|
+
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable or disabled, omit \`mode\` and proceed with standard \`context_smart\` (the API will fall back).
|
|
7531
|
+
|
|
7532
|
+
**Tool naming:** Use the exact tool names exposed by your MCP client. Claude Code typically uses \`mcp__<server>__<tool>\` where \`<server>\` matches your MCP config (often \`contextstream\`). If a tool call fails with "No such tool available", refresh rules and match the tool list.
|
|
7531
7533
|
|
|
7532
7534
|
---
|
|
7533
7535
|
|
|
@@ -7619,9 +7621,9 @@ Only after this preflight, proceed with search/analysis below.
|
|
|
7619
7621
|
2. \`search(mode="hybrid", query="...")\` or \`search(mode="keyword", query="<filename>")\`
|
|
7620
7622
|
3. \`project(action="files")\` - file tree/list (only when needed)
|
|
7621
7623
|
4. \`graph(action="dependencies", ...)\` - code structure
|
|
7622
|
-
5. Local repo scans (rg/ls/find) - only if ContextStream returns no results or
|
|
7624
|
+
5. Local repo scans (rg/ls/find) - only if ContextStream returns no results, errors, or the user explicitly asks
|
|
7623
7625
|
|
|
7624
|
-
|
|
7626
|
+
If ContextStream returns results, stop and use them. Do NOT use local Search/Explore/Read unless you need exact code edits.
|
|
7625
7627
|
|
|
7626
7628
|
**Code Analysis:**
|
|
7627
7629
|
- Dependencies: \`graph(action="dependencies", file_path="...")\`
|
|
@@ -7713,7 +7715,9 @@ Rules Version: ${RULES_VERSION}
|
|
|
7713
7715
|
| **Before risky work** | \`session(action="get_lessons", query="<topic>")\` |
|
|
7714
7716
|
| **On user frustration** | \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\` |
|
|
7715
7717
|
|
|
7716
|
-
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable, omit \`mode
|
|
7718
|
+
**Context Pack (Pro+):** If enabled, use \`context_smart(..., mode="pack", distill=true)\` for code/file queries. If unavailable or disabled, omit \`mode\` and proceed with standard \`context_smart\` (the API will fall back).
|
|
7719
|
+
|
|
7720
|
+
**Tool naming:** Use the exact tool names exposed by your MCP client. Claude Code typically uses \`mcp__<server>__<tool>\` where \`<server>\` matches your MCP config (often \`contextstream\`). If a tool call fails with "No such tool available", refresh rules and match the tool list.
|
|
7717
7721
|
|
|
7718
7722
|
### Quick Reference: Domain Tools
|
|
7719
7723
|
|
|
@@ -7735,6 +7739,7 @@ Rules Version: ${RULES_VERSION}
|
|
|
7735
7739
|
- **Before searching files/code**: Check \`project(action="index_status")\`; if missing/stale run \`project(action="ingest_local", path="<cwd>")\` or \`project(action="index")\`, and use \`graph(action="ingest")\` if needed
|
|
7736
7740
|
- **For discovery**: Use \`session(action="smart_search")\` or \`search(mode="hybrid")\` before any local repo scans
|
|
7737
7741
|
- **For file/function/config lookups**: Use \`search\`/\`graph\` first; only fall back to rg/ls/find if ContextStream returns no results
|
|
7742
|
+
- **If ContextStream returns results**: Do NOT use local Search/Explore/Read; only open specific files when needed for exact edits
|
|
7738
7743
|
- **For code analysis**: Use \`graph(action="dependencies")\` or \`graph(action="impact")\` for call/dependency analysis
|
|
7739
7744
|
- **On [RULES_NOTICE]**: Use \`generate_editor_rules(folder_path="<cwd>")\` to update rules
|
|
7740
7745
|
- **After completing work**: Always capture decisions/insights with \`session(action="capture")\`
|
|
@@ -8050,6 +8055,7 @@ var DEFAULT_PARAM_DESCRIPTIONS = {
|
|
|
8050
8055
|
recurrence: "Recurrence pattern (daily, weekly, monthly).",
|
|
8051
8056
|
keywords: "Keywords for matching.",
|
|
8052
8057
|
overwrite: "Allow overwriting existing files on disk.",
|
|
8058
|
+
overwrite_existing: "Allow overwriting existing rule files (ContextStream block only).",
|
|
8053
8059
|
write_to_disk: "Write ingested files to disk before indexing.",
|
|
8054
8060
|
await_indexing: "Wait for indexing to finish before returning.",
|
|
8055
8061
|
auto_index: "Automatically index on creation.",
|
|
@@ -8227,11 +8233,159 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
8227
8233
|
}
|
|
8228
8234
|
var CONTEXTSTREAM_START_MARKER = "<!-- BEGIN ContextStream -->";
|
|
8229
8235
|
var CONTEXTSTREAM_END_MARKER = "<!-- END ContextStream -->";
|
|
8236
|
+
var LEGACY_CONTEXTSTREAM_HINTS = [
|
|
8237
|
+
"contextstream integration",
|
|
8238
|
+
"contextstream v0.4",
|
|
8239
|
+
"contextstream v0.3",
|
|
8240
|
+
"contextstream (standard)",
|
|
8241
|
+
"contextstream (consolidated",
|
|
8242
|
+
"contextstream mcp",
|
|
8243
|
+
"contextstream tools"
|
|
8244
|
+
];
|
|
8245
|
+
var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS = [
|
|
8246
|
+
"contextstream",
|
|
8247
|
+
"tl;dr",
|
|
8248
|
+
"required every message",
|
|
8249
|
+
"quick reference",
|
|
8250
|
+
"tool catalog",
|
|
8251
|
+
"consolidated domain tools",
|
|
8252
|
+
"standalone tools",
|
|
8253
|
+
"domain tools",
|
|
8254
|
+
"why context_smart",
|
|
8255
|
+
"recommended token budgets",
|
|
8256
|
+
"rules update notices",
|
|
8257
|
+
"preferences & lessons",
|
|
8258
|
+
"index & graph preflight",
|
|
8259
|
+
"search & code intelligence",
|
|
8260
|
+
"distillation",
|
|
8261
|
+
"when to capture",
|
|
8262
|
+
"behavior rules",
|
|
8263
|
+
"plans & tasks",
|
|
8264
|
+
"complete action reference"
|
|
8265
|
+
];
|
|
8266
|
+
var CONTEXTSTREAM_PREAMBLE_PATTERNS = [
|
|
8267
|
+
/^#\s+workspace:/i,
|
|
8268
|
+
/^#\s+project:/i,
|
|
8269
|
+
/^#\s+workspace id:/i,
|
|
8270
|
+
/^#\s+codex cli instructions$/i,
|
|
8271
|
+
/^#\s+claude code instructions$/i,
|
|
8272
|
+
/^#\s+cursor rules$/i,
|
|
8273
|
+
/^#\s+windsurf rules$/i,
|
|
8274
|
+
/^#\s+cline rules$/i,
|
|
8275
|
+
/^#\s+kilo code rules$/i,
|
|
8276
|
+
/^#\s+roo code rules$/i,
|
|
8277
|
+
/^#\s+aider configuration$/i
|
|
8278
|
+
];
|
|
8230
8279
|
function wrapWithMarkers(content) {
|
|
8231
8280
|
return `${CONTEXTSTREAM_START_MARKER}
|
|
8232
8281
|
${content.trim()}
|
|
8233
8282
|
${CONTEXTSTREAM_END_MARKER}`;
|
|
8234
8283
|
}
|
|
8284
|
+
function isLegacyContextStreamRules(content) {
|
|
8285
|
+
const lower = content.toLowerCase();
|
|
8286
|
+
if (!lower.includes("contextstream")) return false;
|
|
8287
|
+
if (!LEGACY_CONTEXTSTREAM_HINTS.some((hint) => lower.includes(hint))) return false;
|
|
8288
|
+
const headingRegex = /^#{1,6}\s+(.+)$/gm;
|
|
8289
|
+
let hasHeading = false;
|
|
8290
|
+
let match;
|
|
8291
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
8292
|
+
hasHeading = true;
|
|
8293
|
+
const heading = match[1].trim().toLowerCase();
|
|
8294
|
+
const allowed = LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS.some((prefix) => heading.startsWith(prefix));
|
|
8295
|
+
if (!allowed) return false;
|
|
8296
|
+
}
|
|
8297
|
+
return hasHeading;
|
|
8298
|
+
}
|
|
8299
|
+
function isContextStreamPreamble(line) {
|
|
8300
|
+
const trimmed = line.trim();
|
|
8301
|
+
if (!trimmed) return false;
|
|
8302
|
+
return CONTEXTSTREAM_PREAMBLE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
8303
|
+
}
|
|
8304
|
+
function findContextStreamHeading(lines) {
|
|
8305
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
8306
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
8307
|
+
const match = headingRegex.exec(lines[i]);
|
|
8308
|
+
if (!match) continue;
|
|
8309
|
+
if (match[2].toLowerCase().includes("contextstream")) {
|
|
8310
|
+
return { index: i, level: match[1].length };
|
|
8311
|
+
}
|
|
8312
|
+
}
|
|
8313
|
+
return null;
|
|
8314
|
+
}
|
|
8315
|
+
function findSectionEnd(lines, startLine, level) {
|
|
8316
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
8317
|
+
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
8318
|
+
const match = headingRegex.exec(lines[i]);
|
|
8319
|
+
if (!match) continue;
|
|
8320
|
+
if (match[1].length <= level) return i;
|
|
8321
|
+
}
|
|
8322
|
+
return lines.length;
|
|
8323
|
+
}
|
|
8324
|
+
function extractContextStreamBlock(content) {
|
|
8325
|
+
const lines = content.split(/\r?\n/);
|
|
8326
|
+
const heading = findContextStreamHeading(lines);
|
|
8327
|
+
if (!heading) return content.trim();
|
|
8328
|
+
const endLine = findSectionEnd(lines, heading.index, heading.level);
|
|
8329
|
+
return lines.slice(heading.index, endLine).join("\n").trim();
|
|
8330
|
+
}
|
|
8331
|
+
function findLegacyContextStreamSection(content) {
|
|
8332
|
+
const lines = content.split(/\r?\n/);
|
|
8333
|
+
const heading = findContextStreamHeading(lines);
|
|
8334
|
+
if (!heading) return null;
|
|
8335
|
+
let startLine = heading.index;
|
|
8336
|
+
for (let i = heading.index - 1; i >= 0; i -= 1) {
|
|
8337
|
+
const line = lines[i];
|
|
8338
|
+
if (!line.trim()) {
|
|
8339
|
+
startLine = i;
|
|
8340
|
+
continue;
|
|
8341
|
+
}
|
|
8342
|
+
if (isContextStreamPreamble(line)) {
|
|
8343
|
+
startLine = i;
|
|
8344
|
+
continue;
|
|
8345
|
+
}
|
|
8346
|
+
break;
|
|
8347
|
+
}
|
|
8348
|
+
const endLine = findSectionEnd(lines, heading.index, heading.level);
|
|
8349
|
+
return { startLine, endLine, contextLine: heading.index };
|
|
8350
|
+
}
|
|
8351
|
+
function blockHasPreamble(block) {
|
|
8352
|
+
const lines = block.split(/\r?\n/);
|
|
8353
|
+
for (const line of lines) {
|
|
8354
|
+
const trimmed = line.trim();
|
|
8355
|
+
if (!trimmed) continue;
|
|
8356
|
+
if (isContextStreamPreamble(trimmed)) return true;
|
|
8357
|
+
if (/^#{1,6}\s+/.test(trimmed)) return false;
|
|
8358
|
+
}
|
|
8359
|
+
return false;
|
|
8360
|
+
}
|
|
8361
|
+
function replaceContextStreamBlock(existing, content) {
|
|
8362
|
+
const fullWrapped = wrapWithMarkers(content);
|
|
8363
|
+
const blockWrapped = wrapWithMarkers(extractContextStreamBlock(content));
|
|
8364
|
+
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER);
|
|
8365
|
+
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER);
|
|
8366
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
8367
|
+
const existingBlock = existing.slice(startIdx + CONTEXTSTREAM_START_MARKER.length, endIdx);
|
|
8368
|
+
const replacement = blockHasPreamble(existingBlock) ? fullWrapped : blockWrapped;
|
|
8369
|
+
const before = existing.substring(0, startIdx).trimEnd();
|
|
8370
|
+
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length).trimStart();
|
|
8371
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
8372
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
8373
|
+
}
|
|
8374
|
+
const legacy = findLegacyContextStreamSection(existing);
|
|
8375
|
+
if (legacy) {
|
|
8376
|
+
const lines = existing.split(/\r?\n/);
|
|
8377
|
+
const before = lines.slice(0, legacy.startLine).join("\n").trimEnd();
|
|
8378
|
+
const after = lines.slice(legacy.endLine).join("\n").trimStart();
|
|
8379
|
+
const replacement = legacy.startLine < legacy.contextLine ? fullWrapped : blockWrapped;
|
|
8380
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
8381
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
8382
|
+
}
|
|
8383
|
+
if (isLegacyContextStreamRules(existing)) {
|
|
8384
|
+
return { content: fullWrapped + "\n", status: "updated" };
|
|
8385
|
+
}
|
|
8386
|
+
const appended = existing.trimEnd() + "\n\n" + blockWrapped + "\n";
|
|
8387
|
+
return { content: appended, status: "appended" };
|
|
8388
|
+
}
|
|
8235
8389
|
async function upsertRuleFile(filePath, content) {
|
|
8236
8390
|
await fs3.promises.mkdir(path4.dirname(filePath), { recursive: true });
|
|
8237
8391
|
const wrappedContent = wrapWithMarkers(content);
|
|
@@ -8244,18 +8398,13 @@ async function upsertRuleFile(filePath, content) {
|
|
|
8244
8398
|
await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
8245
8399
|
return "created";
|
|
8246
8400
|
}
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
8250
|
-
const before = existing.substring(0, startIdx);
|
|
8251
|
-
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER.length);
|
|
8252
|
-
const updated = before.trimEnd() + "\n\n" + wrappedContent + "\n" + after.trimStart();
|
|
8253
|
-
await fs3.promises.writeFile(filePath, updated.trim() + "\n", "utf8");
|
|
8401
|
+
if (!existing.trim()) {
|
|
8402
|
+
await fs3.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
8254
8403
|
return "updated";
|
|
8255
8404
|
}
|
|
8256
|
-
const
|
|
8257
|
-
await fs3.promises.writeFile(filePath,
|
|
8258
|
-
return
|
|
8405
|
+
const replaced = replaceContextStreamBlock(existing, content);
|
|
8406
|
+
await fs3.promises.writeFile(filePath, replaced.content, "utf8");
|
|
8407
|
+
return replaced.status;
|
|
8259
8408
|
}
|
|
8260
8409
|
async function writeEditorRules(options) {
|
|
8261
8410
|
const editors = options.editors && options.editors.length > 0 ? options.editors : getAvailableEditors();
|
|
@@ -8273,6 +8422,10 @@ async function writeEditorRules(options) {
|
|
|
8273
8422
|
continue;
|
|
8274
8423
|
}
|
|
8275
8424
|
const filePath = path4.join(options.folderPath, rule.filename);
|
|
8425
|
+
if (fs3.existsSync(filePath) && !options.overwriteExisting) {
|
|
8426
|
+
results.push({ editor, filename: rule.filename, status: "skipped (exists)" });
|
|
8427
|
+
continue;
|
|
8428
|
+
}
|
|
8276
8429
|
try {
|
|
8277
8430
|
const status = await upsertRuleFile(filePath, rule.content);
|
|
8278
8431
|
results.push({ editor, filename: rule.filename, status });
|
|
@@ -9986,7 +10139,8 @@ Access: Free`,
|
|
|
9986
10139
|
description: external_exports.string().optional().describe("Project description"),
|
|
9987
10140
|
workspace_id: external_exports.string().uuid().optional().describe("Workspace ID (uses current session workspace if not provided)"),
|
|
9988
10141
|
folder_path: external_exports.string().optional().describe("Optional: Local folder path to associate with this project"),
|
|
9989
|
-
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in folder_path (requires folder_path)")
|
|
10142
|
+
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in folder_path (requires folder_path)"),
|
|
10143
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules")
|
|
9990
10144
|
})
|
|
9991
10145
|
},
|
|
9992
10146
|
async (input) => {
|
|
@@ -10001,6 +10155,7 @@ Access: Free`,
|
|
|
10001
10155
|
});
|
|
10002
10156
|
const projectData = result;
|
|
10003
10157
|
let rulesGenerated = [];
|
|
10158
|
+
let rulesSkipped = [];
|
|
10004
10159
|
if (input.folder_path && projectData.id) {
|
|
10005
10160
|
try {
|
|
10006
10161
|
const configDir = path4.join(input.folder_path, ".contextstream");
|
|
@@ -10019,9 +10174,11 @@ Access: Free`,
|
|
|
10019
10174
|
folderPath: input.folder_path,
|
|
10020
10175
|
editors: getAvailableEditors(),
|
|
10021
10176
|
workspaceId,
|
|
10022
|
-
projectName: input.name
|
|
10177
|
+
projectName: input.name,
|
|
10178
|
+
overwriteExisting: input.overwrite_existing
|
|
10023
10179
|
});
|
|
10024
10180
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
10181
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
10025
10182
|
}
|
|
10026
10183
|
} catch (err) {
|
|
10027
10184
|
console.error("[ContextStream] Failed to write project config:", err);
|
|
@@ -10031,7 +10188,8 @@ Access: Free`,
|
|
|
10031
10188
|
...result && typeof result === "object" ? result : {},
|
|
10032
10189
|
folder_path: input.folder_path,
|
|
10033
10190
|
config_written: input.folder_path ? true : void 0,
|
|
10034
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
10191
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
10192
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
10035
10193
|
};
|
|
10036
10194
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
10037
10195
|
}
|
|
@@ -11155,24 +11313,29 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
|
|
|
11155
11313
|
workspace_id: external_exports.string().uuid().describe("Workspace ID to associate with"),
|
|
11156
11314
|
workspace_name: external_exports.string().optional().describe("Workspace name for reference"),
|
|
11157
11315
|
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)"),
|
|
11158
|
-
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules for Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, and Aider")
|
|
11316
|
+
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules for Windsurf, Cursor, Cline, Kilo Code, Roo Code, Claude Code, and Aider"),
|
|
11317
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules")
|
|
11159
11318
|
})
|
|
11160
11319
|
},
|
|
11161
11320
|
async (input) => {
|
|
11162
11321
|
const result = await client.associateWorkspace(input);
|
|
11163
11322
|
let rulesGenerated = [];
|
|
11323
|
+
let rulesSkipped = [];
|
|
11164
11324
|
if (input.generate_editor_rules) {
|
|
11165
11325
|
const ruleResults = await writeEditorRules({
|
|
11166
11326
|
folderPath: input.folder_path,
|
|
11167
11327
|
editors: getAvailableEditors(),
|
|
11168
11328
|
workspaceName: input.workspace_name,
|
|
11169
|
-
workspaceId: input.workspace_id
|
|
11329
|
+
workspaceId: input.workspace_id,
|
|
11330
|
+
overwriteExisting: input.overwrite_existing
|
|
11170
11331
|
});
|
|
11171
11332
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
11333
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
11172
11334
|
}
|
|
11173
11335
|
const response = {
|
|
11174
11336
|
...result,
|
|
11175
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
11337
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
11338
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
11176
11339
|
};
|
|
11177
11340
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
11178
11341
|
}
|
|
@@ -11195,6 +11358,7 @@ Behavior:
|
|
|
11195
11358
|
visibility: external_exports.enum(["private", "public"]).optional().describe("Workspace visibility (default: private)"),
|
|
11196
11359
|
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/company/* -> workspace)"),
|
|
11197
11360
|
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in the folder for automatic ContextStream usage"),
|
|
11361
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files when generating editor rules"),
|
|
11198
11362
|
context_hint: external_exports.string().optional().describe("Optional context hint for session initialization"),
|
|
11199
11363
|
auto_index: external_exports.boolean().optional().describe("Automatically create and index project from folder (default: true)")
|
|
11200
11364
|
})
|
|
@@ -11247,14 +11411,17 @@ Behavior:
|
|
|
11247
11411
|
create_parent_mapping: input.create_parent_mapping
|
|
11248
11412
|
});
|
|
11249
11413
|
let rulesGenerated = [];
|
|
11414
|
+
let rulesSkipped = [];
|
|
11250
11415
|
if (input.generate_editor_rules) {
|
|
11251
11416
|
const ruleResults = await writeEditorRules({
|
|
11252
11417
|
folderPath,
|
|
11253
11418
|
editors: getAvailableEditors(),
|
|
11254
11419
|
workspaceName: newWorkspace.name || input.workspace_name,
|
|
11255
|
-
workspaceId: newWorkspace.id
|
|
11420
|
+
workspaceId: newWorkspace.id,
|
|
11421
|
+
overwriteExisting: input.overwrite_existing
|
|
11256
11422
|
});
|
|
11257
11423
|
rulesGenerated = ruleResults.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").map((r) => r.status === "created" ? r.filename : `${r.filename} (${r.status})`);
|
|
11424
|
+
rulesSkipped = ruleResults.filter((r) => r.status.startsWith("skipped")).map((r) => r.filename);
|
|
11258
11425
|
}
|
|
11259
11426
|
const session = await client.initSession(
|
|
11260
11427
|
{
|
|
@@ -11279,7 +11446,8 @@ Behavior:
|
|
|
11279
11446
|
name: newWorkspace.name || input.workspace_name
|
|
11280
11447
|
},
|
|
11281
11448
|
association: associateResult,
|
|
11282
|
-
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
11449
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0,
|
|
11450
|
+
editor_rules_skipped: rulesSkipped.length > 0 ? rulesSkipped : void 0
|
|
11283
11451
|
}
|
|
11284
11452
|
};
|
|
11285
11453
|
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
@@ -11666,6 +11834,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
11666
11834
|
project_name: external_exports.string().optional().describe("Project name to include in rules"),
|
|
11667
11835
|
additional_rules: external_exports.string().optional().describe("Additional project-specific rules to append"),
|
|
11668
11836
|
mode: external_exports.enum(["minimal", "full"]).optional().describe("Rule verbosity mode (default: minimal)"),
|
|
11837
|
+
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files (ContextStream block only)"),
|
|
11669
11838
|
dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
|
|
11670
11839
|
})
|
|
11671
11840
|
},
|
|
@@ -11704,14 +11873,17 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
11704
11873
|
workspaceId: input.workspace_id,
|
|
11705
11874
|
projectName: input.project_name,
|
|
11706
11875
|
additionalRules: input.additional_rules,
|
|
11707
|
-
mode: input.mode
|
|
11876
|
+
mode: input.mode,
|
|
11877
|
+
overwriteExisting: input.overwrite_existing
|
|
11708
11878
|
});
|
|
11709
11879
|
results.push(...writeResults);
|
|
11710
11880
|
}
|
|
11881
|
+
const createdCount = results.filter((r) => r.status === "created" || r.status === "updated" || r.status === "appended").length;
|
|
11882
|
+
const skippedCount = results.filter((r) => r.status.startsWith("skipped")).length;
|
|
11711
11883
|
const summary = {
|
|
11712
11884
|
folder: folderPath,
|
|
11713
11885
|
results,
|
|
11714
|
-
message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : `Generated ${
|
|
11886
|
+
message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : skippedCount > 0 ? `Generated ${createdCount} rule files. ${skippedCount} skipped (existing files). Re-run with overwrite_existing: true to replace ContextStream blocks.` : `Generated ${createdCount} rule files.`
|
|
11715
11887
|
};
|
|
11716
11888
|
return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
|
|
11717
11889
|
}
|
|
@@ -15738,11 +15910,159 @@ async function fileExists(filePath) {
|
|
|
15738
15910
|
}
|
|
15739
15911
|
var CONTEXTSTREAM_START_MARKER2 = "<!-- BEGIN ContextStream -->";
|
|
15740
15912
|
var CONTEXTSTREAM_END_MARKER2 = "<!-- END ContextStream -->";
|
|
15913
|
+
var LEGACY_CONTEXTSTREAM_HINTS2 = [
|
|
15914
|
+
"contextstream integration",
|
|
15915
|
+
"contextstream v0.4",
|
|
15916
|
+
"contextstream v0.3",
|
|
15917
|
+
"contextstream (standard)",
|
|
15918
|
+
"contextstream (consolidated",
|
|
15919
|
+
"contextstream mcp",
|
|
15920
|
+
"contextstream tools"
|
|
15921
|
+
];
|
|
15922
|
+
var LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS2 = [
|
|
15923
|
+
"contextstream",
|
|
15924
|
+
"tl;dr",
|
|
15925
|
+
"required every message",
|
|
15926
|
+
"quick reference",
|
|
15927
|
+
"tool catalog",
|
|
15928
|
+
"consolidated domain tools",
|
|
15929
|
+
"standalone tools",
|
|
15930
|
+
"domain tools",
|
|
15931
|
+
"why context_smart",
|
|
15932
|
+
"recommended token budgets",
|
|
15933
|
+
"rules update notices",
|
|
15934
|
+
"preferences & lessons",
|
|
15935
|
+
"index & graph preflight",
|
|
15936
|
+
"search & code intelligence",
|
|
15937
|
+
"distillation",
|
|
15938
|
+
"when to capture",
|
|
15939
|
+
"behavior rules",
|
|
15940
|
+
"plans & tasks",
|
|
15941
|
+
"complete action reference"
|
|
15942
|
+
];
|
|
15943
|
+
var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
|
|
15944
|
+
/^#\s+workspace:/i,
|
|
15945
|
+
/^#\s+project:/i,
|
|
15946
|
+
/^#\s+workspace id:/i,
|
|
15947
|
+
/^#\s+codex cli instructions$/i,
|
|
15948
|
+
/^#\s+claude code instructions$/i,
|
|
15949
|
+
/^#\s+cursor rules$/i,
|
|
15950
|
+
/^#\s+windsurf rules$/i,
|
|
15951
|
+
/^#\s+cline rules$/i,
|
|
15952
|
+
/^#\s+kilo code rules$/i,
|
|
15953
|
+
/^#\s+roo code rules$/i,
|
|
15954
|
+
/^#\s+aider configuration$/i
|
|
15955
|
+
];
|
|
15741
15956
|
function wrapWithMarkers2(content) {
|
|
15742
15957
|
return `${CONTEXTSTREAM_START_MARKER2}
|
|
15743
15958
|
${content.trim()}
|
|
15744
15959
|
${CONTEXTSTREAM_END_MARKER2}`;
|
|
15745
15960
|
}
|
|
15961
|
+
function isLegacyContextStreamRules2(content) {
|
|
15962
|
+
const lower = content.toLowerCase();
|
|
15963
|
+
if (!lower.includes("contextstream")) return false;
|
|
15964
|
+
if (!LEGACY_CONTEXTSTREAM_HINTS2.some((hint) => lower.includes(hint))) return false;
|
|
15965
|
+
const headingRegex = /^#{1,6}\s+(.+)$/gm;
|
|
15966
|
+
let hasHeading = false;
|
|
15967
|
+
let match;
|
|
15968
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
15969
|
+
hasHeading = true;
|
|
15970
|
+
const heading = match[1].trim().toLowerCase();
|
|
15971
|
+
const allowed = LEGACY_CONTEXTSTREAM_ALLOWED_HEADINGS2.some((prefix) => heading.startsWith(prefix));
|
|
15972
|
+
if (!allowed) return false;
|
|
15973
|
+
}
|
|
15974
|
+
return hasHeading;
|
|
15975
|
+
}
|
|
15976
|
+
function isContextStreamPreamble2(line) {
|
|
15977
|
+
const trimmed = line.trim();
|
|
15978
|
+
if (!trimmed) return false;
|
|
15979
|
+
return CONTEXTSTREAM_PREAMBLE_PATTERNS2.some((pattern) => pattern.test(trimmed));
|
|
15980
|
+
}
|
|
15981
|
+
function findContextStreamHeading2(lines) {
|
|
15982
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
15983
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
15984
|
+
const match = headingRegex.exec(lines[i]);
|
|
15985
|
+
if (!match) continue;
|
|
15986
|
+
if (match[2].toLowerCase().includes("contextstream")) {
|
|
15987
|
+
return { index: i, level: match[1].length };
|
|
15988
|
+
}
|
|
15989
|
+
}
|
|
15990
|
+
return null;
|
|
15991
|
+
}
|
|
15992
|
+
function findSectionEnd2(lines, startLine, level) {
|
|
15993
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/;
|
|
15994
|
+
for (let i = startLine + 1; i < lines.length; i += 1) {
|
|
15995
|
+
const match = headingRegex.exec(lines[i]);
|
|
15996
|
+
if (!match) continue;
|
|
15997
|
+
if (match[1].length <= level) return i;
|
|
15998
|
+
}
|
|
15999
|
+
return lines.length;
|
|
16000
|
+
}
|
|
16001
|
+
function extractContextStreamBlock2(content) {
|
|
16002
|
+
const lines = content.split(/\r?\n/);
|
|
16003
|
+
const heading = findContextStreamHeading2(lines);
|
|
16004
|
+
if (!heading) return content.trim();
|
|
16005
|
+
const endLine = findSectionEnd2(lines, heading.index, heading.level);
|
|
16006
|
+
return lines.slice(heading.index, endLine).join("\n").trim();
|
|
16007
|
+
}
|
|
16008
|
+
function findLegacyContextStreamSection2(content) {
|
|
16009
|
+
const lines = content.split(/\r?\n/);
|
|
16010
|
+
const heading = findContextStreamHeading2(lines);
|
|
16011
|
+
if (!heading) return null;
|
|
16012
|
+
let startLine = heading.index;
|
|
16013
|
+
for (let i = heading.index - 1; i >= 0; i -= 1) {
|
|
16014
|
+
const line = lines[i];
|
|
16015
|
+
if (!line.trim()) {
|
|
16016
|
+
startLine = i;
|
|
16017
|
+
continue;
|
|
16018
|
+
}
|
|
16019
|
+
if (isContextStreamPreamble2(line)) {
|
|
16020
|
+
startLine = i;
|
|
16021
|
+
continue;
|
|
16022
|
+
}
|
|
16023
|
+
break;
|
|
16024
|
+
}
|
|
16025
|
+
const endLine = findSectionEnd2(lines, heading.index, heading.level);
|
|
16026
|
+
return { startLine, endLine, contextLine: heading.index };
|
|
16027
|
+
}
|
|
16028
|
+
function blockHasPreamble2(block) {
|
|
16029
|
+
const lines = block.split(/\r?\n/);
|
|
16030
|
+
for (const line of lines) {
|
|
16031
|
+
const trimmed = line.trim();
|
|
16032
|
+
if (!trimmed) continue;
|
|
16033
|
+
if (isContextStreamPreamble2(trimmed)) return true;
|
|
16034
|
+
if (/^#{1,6}\s+/.test(trimmed)) return false;
|
|
16035
|
+
}
|
|
16036
|
+
return false;
|
|
16037
|
+
}
|
|
16038
|
+
function replaceContextStreamBlock2(existing, content) {
|
|
16039
|
+
const fullWrapped = wrapWithMarkers2(content);
|
|
16040
|
+
const blockWrapped = wrapWithMarkers2(extractContextStreamBlock2(content));
|
|
16041
|
+
const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER2);
|
|
16042
|
+
const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER2);
|
|
16043
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
16044
|
+
const existingBlock = existing.slice(startIdx + CONTEXTSTREAM_START_MARKER2.length, endIdx);
|
|
16045
|
+
const replacement = blockHasPreamble2(existingBlock) ? fullWrapped : blockWrapped;
|
|
16046
|
+
const before = existing.substring(0, startIdx).trimEnd();
|
|
16047
|
+
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER2.length).trimStart();
|
|
16048
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
16049
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
16050
|
+
}
|
|
16051
|
+
const legacy = findLegacyContextStreamSection2(existing);
|
|
16052
|
+
if (legacy) {
|
|
16053
|
+
const lines = existing.split(/\r?\n/);
|
|
16054
|
+
const before = lines.slice(0, legacy.startLine).join("\n").trimEnd();
|
|
16055
|
+
const after = lines.slice(legacy.endLine).join("\n").trimStart();
|
|
16056
|
+
const replacement = legacy.startLine < legacy.contextLine ? fullWrapped : blockWrapped;
|
|
16057
|
+
const merged = [before, replacement, after].filter((part) => part.length > 0).join("\n\n");
|
|
16058
|
+
return { content: merged.trim() + "\n", status: "updated" };
|
|
16059
|
+
}
|
|
16060
|
+
if (isLegacyContextStreamRules2(existing)) {
|
|
16061
|
+
return { content: fullWrapped + "\n", status: "updated" };
|
|
16062
|
+
}
|
|
16063
|
+
const appended = existing.trimEnd() + "\n\n" + blockWrapped + "\n";
|
|
16064
|
+
return { content: appended, status: "appended" };
|
|
16065
|
+
}
|
|
15746
16066
|
async function upsertTextFile(filePath, content, _marker) {
|
|
15747
16067
|
await fs5.mkdir(path6.dirname(filePath), { recursive: true });
|
|
15748
16068
|
const exists = await fileExists(filePath);
|
|
@@ -15752,23 +16072,13 @@ async function upsertTextFile(filePath, content, _marker) {
|
|
|
15752
16072
|
return "created";
|
|
15753
16073
|
}
|
|
15754
16074
|
const existing = await fs5.readFile(filePath, "utf8").catch(() => "");
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
15758
|
-
const before = existing.substring(0, startIdx);
|
|
15759
|
-
const after = existing.substring(endIdx + CONTEXTSTREAM_END_MARKER2.length);
|
|
15760
|
-
const updated = before.trimEnd() + "\n\n" + wrappedContent + "\n" + after.trimStart();
|
|
15761
|
-
await fs5.writeFile(filePath, updated.trim() + "\n", "utf8");
|
|
15762
|
-
return "updated";
|
|
15763
|
-
}
|
|
15764
|
-
if (existing.includes("ContextStream")) {
|
|
15765
|
-
const joined2 = existing.trimEnd() + "\n\n" + wrappedContent + "\n";
|
|
15766
|
-
await fs5.writeFile(filePath, joined2, "utf8");
|
|
16075
|
+
if (!existing.trim()) {
|
|
16076
|
+
await fs5.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
15767
16077
|
return "updated";
|
|
15768
16078
|
}
|
|
15769
|
-
const
|
|
15770
|
-
await fs5.writeFile(filePath,
|
|
15771
|
-
return
|
|
16079
|
+
const replaced = replaceContextStreamBlock2(existing, content);
|
|
16080
|
+
await fs5.writeFile(filePath, replaced.content, "utf8");
|
|
16081
|
+
return replaced.status;
|
|
15772
16082
|
}
|
|
15773
16083
|
function globalRulesPathForEditor(editor) {
|
|
15774
16084
|
const home = homedir4();
|
|
@@ -16153,6 +16463,27 @@ async function runSetupWizard(args) {
|
|
|
16153
16463
|
const dryRun = args.includes("--dry-run");
|
|
16154
16464
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
16155
16465
|
const writeActions = [];
|
|
16466
|
+
let overwriteAllRules = null;
|
|
16467
|
+
let skipAllRules = false;
|
|
16468
|
+
const confirmOverwriteRules = async (filePath) => {
|
|
16469
|
+
if (dryRun) return true;
|
|
16470
|
+
if (skipAllRules) return false;
|
|
16471
|
+
if (overwriteAllRules) return true;
|
|
16472
|
+
const exists = await fileExists(filePath);
|
|
16473
|
+
if (!exists) return true;
|
|
16474
|
+
const answer = normalizeInput(
|
|
16475
|
+
await rl.question(`Rules file already exists at ${filePath}. Replace ContextStream block? [y/N/a/s]: `)
|
|
16476
|
+
).toLowerCase();
|
|
16477
|
+
if (answer === "a" || answer === "all") {
|
|
16478
|
+
overwriteAllRules = true;
|
|
16479
|
+
return true;
|
|
16480
|
+
}
|
|
16481
|
+
if (answer === "s" || answer === "skip-all" || answer === "none") {
|
|
16482
|
+
skipAllRules = true;
|
|
16483
|
+
return false;
|
|
16484
|
+
}
|
|
16485
|
+
return answer === "y" || answer === "yes";
|
|
16486
|
+
};
|
|
16156
16487
|
try {
|
|
16157
16488
|
console.log(`ContextStream Setup Wizard (v${VERSION})`);
|
|
16158
16489
|
console.log("This configures ContextStream MCP + rules for your AI editor(s).");
|
|
@@ -16525,6 +16856,12 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
16525
16856
|
console.log(`- ${EDITOR_LABELS[editor]}: would write ${filePath}`);
|
|
16526
16857
|
continue;
|
|
16527
16858
|
}
|
|
16859
|
+
const allowOverwrite = await confirmOverwriteRules(filePath);
|
|
16860
|
+
if (!allowOverwrite) {
|
|
16861
|
+
writeActions.push({ kind: "rules", target: filePath, status: "skipped" });
|
|
16862
|
+
console.log(`- ${EDITOR_LABELS[editor]}: skipped ${filePath}`);
|
|
16863
|
+
continue;
|
|
16864
|
+
}
|
|
16528
16865
|
const status = await upsertTextFile(filePath, rule.content, "ContextStream");
|
|
16529
16866
|
writeActions.push({ kind: "rules", target: filePath, status });
|
|
16530
16867
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
@@ -16660,6 +16997,11 @@ Applying to ${projects.length} project(s)...`);
|
|
|
16660
16997
|
continue;
|
|
16661
16998
|
}
|
|
16662
16999
|
try {
|
|
17000
|
+
const allowOverwrite = await confirmOverwriteRules(filePath);
|
|
17001
|
+
if (!allowOverwrite) {
|
|
17002
|
+
writeActions.push({ kind: "rules", target: filePath, status: "skipped" });
|
|
17003
|
+
continue;
|
|
17004
|
+
}
|
|
16663
17005
|
const status = await upsertTextFile(filePath, rule.content, "ContextStream");
|
|
16664
17006
|
writeActions.push({ kind: "rules", target: filePath, status });
|
|
16665
17007
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextstream/mcp-server",
|
|
3
3
|
"mcpName": "io.github.contextstreamio/mcp-server",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.20",
|
|
5
5
|
"description": "ContextStream MCP server - v0.4.x with consolidated domain tools (~11 tools, ~75% token reduction). Code context, memory, search, and AI tools.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|