@contextstream/mcp-server 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +486 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4449,6 +4449,106 @@ function resolveWorkspace(repoPath) {
4449
4449
  return { config: null, source: "ambiguous" };
4450
4450
  }
4451
4451
 
4452
+ // src/cache.ts
4453
+ var MemoryCache = class {
4454
+ constructor(cleanupIntervalMs = 6e4) {
4455
+ this.cache = /* @__PURE__ */ new Map();
4456
+ this.cleanupInterval = null;
4457
+ this.cleanupInterval = setInterval(() => this.cleanup(), cleanupIntervalMs);
4458
+ }
4459
+ /**
4460
+ * Get a cached value if it exists and hasn't expired
4461
+ */
4462
+ get(key) {
4463
+ const entry = this.cache.get(key);
4464
+ if (!entry) return void 0;
4465
+ if (Date.now() > entry.expiresAt) {
4466
+ this.cache.delete(key);
4467
+ return void 0;
4468
+ }
4469
+ return entry.value;
4470
+ }
4471
+ /**
4472
+ * Set a value with TTL in milliseconds
4473
+ */
4474
+ set(key, value, ttlMs) {
4475
+ this.cache.set(key, {
4476
+ value,
4477
+ expiresAt: Date.now() + ttlMs
4478
+ });
4479
+ }
4480
+ /**
4481
+ * Delete a specific key
4482
+ */
4483
+ delete(key) {
4484
+ return this.cache.delete(key);
4485
+ }
4486
+ /**
4487
+ * Delete all keys matching a prefix
4488
+ */
4489
+ deleteByPrefix(prefix) {
4490
+ let deleted = 0;
4491
+ for (const key of this.cache.keys()) {
4492
+ if (key.startsWith(prefix)) {
4493
+ this.cache.delete(key);
4494
+ deleted++;
4495
+ }
4496
+ }
4497
+ return deleted;
4498
+ }
4499
+ /**
4500
+ * Clear all cached entries
4501
+ */
4502
+ clear() {
4503
+ this.cache.clear();
4504
+ }
4505
+ /**
4506
+ * Remove expired entries
4507
+ */
4508
+ cleanup() {
4509
+ const now = Date.now();
4510
+ for (const [key, entry] of this.cache.entries()) {
4511
+ if (now > entry.expiresAt) {
4512
+ this.cache.delete(key);
4513
+ }
4514
+ }
4515
+ }
4516
+ /**
4517
+ * Stop the cleanup interval
4518
+ */
4519
+ destroy() {
4520
+ if (this.cleanupInterval) {
4521
+ clearInterval(this.cleanupInterval);
4522
+ this.cleanupInterval = null;
4523
+ }
4524
+ this.cache.clear();
4525
+ }
4526
+ };
4527
+ var CacheTTL = {
4528
+ // Workspace info rarely changes - cache for 5 minutes
4529
+ WORKSPACE: 5 * 60 * 1e3,
4530
+ // Project info rarely changes - cache for 5 minutes
4531
+ PROJECT: 5 * 60 * 1e3,
4532
+ // Session init context - cache for 60 seconds
4533
+ SESSION_INIT: 60 * 1e3,
4534
+ // Memory events - cache for 30 seconds (they change more often)
4535
+ MEMORY_EVENTS: 30 * 1e3,
4536
+ // Search results - cache for 60 seconds
4537
+ SEARCH: 60 * 1e3,
4538
+ // User preferences - cache for 5 minutes
4539
+ USER_PREFS: 5 * 60 * 1e3
4540
+ };
4541
+ var CacheKeys = {
4542
+ workspace: (id) => `workspace:${id}`,
4543
+ workspaceList: (userId) => `workspaces:${userId}`,
4544
+ project: (id) => `project:${id}`,
4545
+ projectList: (workspaceId) => `projects:${workspaceId}`,
4546
+ sessionInit: (workspaceId, projectId) => `session_init:${workspaceId || ""}:${projectId || ""}`,
4547
+ memoryEvents: (workspaceId) => `memory:${workspaceId}`,
4548
+ search: (query, workspaceId) => `search:${workspaceId || ""}:${query}`
4549
+ };
4550
+ var globalCache = new MemoryCache();
4551
+
4452
4552
  // src/client.ts
4453
4553
  var uuidSchema = external_exports.string().uuid();
4454
4554
  var ContextStreamClient = class {
@@ -4587,14 +4687,24 @@ var ContextStreamClient = class {
4587
4687
  aiEnhancedContext(body) {
4588
4688
  return request(this.config, "/ai/context/enhanced", { body: this.withDefaults(body) });
4589
4689
  }
4590
- // Project extended operations
4591
- getProject(projectId) {
4690
+ // Project extended operations (with caching)
4691
+ async getProject(projectId) {
4592
4692
  uuidSchema.parse(projectId);
4593
- return request(this.config, `/projects/${projectId}`, { method: "GET" });
4693
+ const cacheKey = CacheKeys.project(projectId);
4694
+ const cached = globalCache.get(cacheKey);
4695
+ if (cached) return cached;
4696
+ const result = await request(this.config, `/projects/${projectId}`, { method: "GET" });
4697
+ globalCache.set(cacheKey, result, CacheTTL.PROJECT);
4698
+ return result;
4594
4699
  }
4595
- projectOverview(projectId) {
4700
+ async projectOverview(projectId) {
4596
4701
  uuidSchema.parse(projectId);
4597
- return request(this.config, `/projects/${projectId}/overview`, { method: "GET" });
4702
+ const cacheKey = `project_overview:${projectId}`;
4703
+ const cached = globalCache.get(cacheKey);
4704
+ if (cached) return cached;
4705
+ const result = await request(this.config, `/projects/${projectId}/overview`, { method: "GET" });
4706
+ globalCache.set(cacheKey, result, CacheTTL.PROJECT);
4707
+ return result;
4598
4708
  }
4599
4709
  projectStatistics(projectId) {
4600
4710
  uuidSchema.parse(projectId);
@@ -4618,14 +4728,24 @@ var ContextStreamClient = class {
4618
4728
  body: { files }
4619
4729
  });
4620
4730
  }
4621
- // Workspace extended operations
4622
- getWorkspace(workspaceId) {
4731
+ // Workspace extended operations (with caching)
4732
+ async getWorkspace(workspaceId) {
4623
4733
  uuidSchema.parse(workspaceId);
4624
- return request(this.config, `/workspaces/${workspaceId}`, { method: "GET" });
4734
+ const cacheKey = CacheKeys.workspace(workspaceId);
4735
+ const cached = globalCache.get(cacheKey);
4736
+ if (cached) return cached;
4737
+ const result = await request(this.config, `/workspaces/${workspaceId}`, { method: "GET" });
4738
+ globalCache.set(cacheKey, result, CacheTTL.WORKSPACE);
4739
+ return result;
4625
4740
  }
4626
- workspaceOverview(workspaceId) {
4741
+ async workspaceOverview(workspaceId) {
4627
4742
  uuidSchema.parse(workspaceId);
4628
- return request(this.config, `/workspaces/${workspaceId}/overview`, { method: "GET" });
4743
+ const cacheKey = `workspace_overview:${workspaceId}`;
4744
+ const cached = globalCache.get(cacheKey);
4745
+ if (cached) return cached;
4746
+ const result = await request(this.config, `/workspaces/${workspaceId}/overview`, { method: "GET" });
4747
+ globalCache.set(cacheKey, result, CacheTTL.WORKSPACE);
4748
+ return result;
4629
4749
  }
4630
4750
  workspaceAnalytics(workspaceId) {
4631
4751
  uuidSchema.parse(workspaceId);
@@ -4833,9 +4953,67 @@ var ContextStreamClient = class {
4833
4953
  context.ide_roots = ideRoots;
4834
4954
  if (workspaceId) {
4835
4955
  try {
4836
- context.workspace = await this.workspaceOverview(workspaceId);
4837
- } catch {
4956
+ const batchedContext = await this._fetchSessionContextBatched({
4957
+ workspace_id: workspaceId,
4958
+ project_id: projectId,
4959
+ session_id: context.session_id,
4960
+ context_hint: params.context_hint,
4961
+ include_recent_memory: params.include_recent_memory !== false,
4962
+ include_decisions: params.include_decisions !== false
4963
+ });
4964
+ if (batchedContext.workspace) {
4965
+ context.workspace = batchedContext.workspace;
4966
+ }
4967
+ if (batchedContext.project) {
4968
+ context.project = batchedContext.project;
4969
+ }
4970
+ if (batchedContext.recent_memory) {
4971
+ context.recent_memory = { items: batchedContext.recent_memory };
4972
+ }
4973
+ if (batchedContext.recent_decisions) {
4974
+ context.recent_decisions = { items: batchedContext.recent_decisions };
4975
+ }
4976
+ if (batchedContext.relevant_context) {
4977
+ context.relevant_context = batchedContext.relevant_context;
4978
+ }
4979
+ } catch (e) {
4980
+ console.error("[ContextStream] Batched endpoint failed, falling back to individual calls:", e);
4981
+ await this._fetchSessionContextFallback(context, workspaceId, projectId, params);
4982
+ }
4983
+ }
4984
+ return context;
4985
+ }
4986
+ /**
4987
+ * Fetch session context using the batched /session/init endpoint.
4988
+ * This is much faster than making 5-6 individual API calls.
4989
+ */
4990
+ async _fetchSessionContextBatched(params) {
4991
+ const cacheKey = CacheKeys.sessionInit(params.workspace_id, params.project_id);
4992
+ const cached = globalCache.get(cacheKey);
4993
+ if (cached) {
4994
+ console.error("[ContextStream] Session context cache HIT");
4995
+ return cached;
4996
+ }
4997
+ const result = await request(this.config, "/session/init", {
4998
+ body: {
4999
+ workspace_id: params.workspace_id,
5000
+ project_id: params.project_id,
5001
+ session_id: params.session_id,
5002
+ include_recent_memory: params.include_recent_memory ?? true,
5003
+ include_decisions: params.include_decisions ?? true
4838
5004
  }
5005
+ });
5006
+ const contextData = "data" in result && result.data ? result.data : result;
5007
+ globalCache.set(cacheKey, contextData, CacheTTL.SESSION_INIT);
5008
+ return contextData;
5009
+ }
5010
+ /**
5011
+ * Fallback to individual API calls if batched endpoint is unavailable.
5012
+ */
5013
+ async _fetchSessionContextFallback(context, workspaceId, projectId, params) {
5014
+ try {
5015
+ context.workspace = await this.workspaceOverview(workspaceId);
5016
+ } catch {
4839
5017
  }
4840
5018
  if (projectId) {
4841
5019
  try {
@@ -4843,39 +5021,34 @@ var ContextStreamClient = class {
4843
5021
  } catch {
4844
5022
  }
4845
5023
  }
4846
- if (params.include_recent_memory !== false && workspaceId) {
5024
+ if (params.include_recent_memory !== false) {
4847
5025
  try {
4848
5026
  context.recent_memory = await this.listMemoryEvents({
4849
5027
  workspace_id: workspaceId,
4850
- // NOTE: Intentionally NOT filtering by project_id
4851
- // to get workspace-wide context
4852
5028
  limit: 10
4853
5029
  });
4854
5030
  } catch {
4855
5031
  }
4856
5032
  }
4857
- if (params.include_decisions !== false && workspaceId) {
5033
+ if (params.include_decisions !== false) {
4858
5034
  try {
4859
5035
  context.recent_decisions = await this.memoryDecisions({
4860
5036
  workspace_id: workspaceId,
4861
- // NOTE: Intentionally NOT filtering by project_id
4862
5037
  limit: 5
4863
5038
  });
4864
5039
  } catch {
4865
5040
  }
4866
5041
  }
4867
- if (params.context_hint && workspaceId) {
5042
+ if (params.context_hint) {
4868
5043
  try {
4869
5044
  context.relevant_context = await this.memorySearch({
4870
5045
  query: params.context_hint,
4871
5046
  workspace_id: workspaceId,
4872
- // Search workspace-wide first
4873
5047
  limit: 5
4874
5048
  });
4875
5049
  } catch {
4876
5050
  }
4877
5051
  }
4878
- return context;
4879
5052
  }
4880
5053
  /**
4881
5054
  * Associate a folder with a workspace (called after user selects from candidates).
@@ -5015,6 +5188,188 @@ var ContextStreamClient = class {
5015
5188
  }
5016
5189
  };
5017
5190
 
5191
+ // src/rules-templates.ts
5192
+ var CONTEXTSTREAM_RULES = `
5193
+ ## ContextStream Integration
5194
+
5195
+ You have access to ContextStream MCP tools for persistent memory and context across sessions.
5196
+
5197
+ ---
5198
+
5199
+ ### \u{1F6A8} ESSENTIAL Tools (Always Use)
5200
+
5201
+ These tools should be called automatically in every conversation:
5202
+
5203
+ | When | Tool | Example Prompt |
5204
+ |------|------|----------------|
5205
+ | **Conversation start** | \`session_init\` | "Load my workspace context" |
5206
+ | **User makes a decision** | \`session_capture\` | "Remember we decided to use PostgreSQL" |
5207
+ | **User states a preference** | \`session_capture\` | "Remember I prefer TypeScript strict mode" |
5208
+ | **Before starting work** | \`session_recall\` | "What did we decide about auth?" |
5209
+
5210
+ ---
5211
+
5212
+ ### \u{1F4DD} Memory & Context Tools (Use Frequently)
5213
+
5214
+ | Tool | Example Prompt |
5215
+ |------|----------------|
5216
+ | \`session_remember\` | "Remember that the API uses JWT tokens" |
5217
+ | \`session_smart_search\` | "Search for everything about payment processing" |
5218
+ | \`memory_search\` | "Search my notes for caching strategy" |
5219
+ | \`memory_decisions\` | "Show me all past decisions" |
5220
+ | \`memory_timeline\` | "Show me the timeline of this workspace" |
5221
+ | \`memory_summary\` | "Summarize what we've worked on" |
5222
+
5223
+ ---
5224
+
5225
+ ### \u{1F50D} Code Search Tools (Use When Exploring Code)
5226
+
5227
+ | Tool | Example Prompt |
5228
+ |------|----------------|
5229
+ | \`search_semantic\` | "How does authentication work in this codebase?" |
5230
+ | \`search_hybrid\` | "Find code related to user permissions" |
5231
+ | \`search_keyword\` | "Search for handleSubmit function" |
5232
+ | \`search_pattern\` | "Find all TODO comments" |
5233
+ | \`ai_context\` | "Build context for implementing user roles" |
5234
+ | \`ai_enhanced_context\` | "Give me deep context on the payment system" |
5235
+
5236
+ ---
5237
+
5238
+ ### \u{1F578}\uFE0F Code Analysis Tools (Use for Architecture)
5239
+
5240
+ | Tool | Example Prompt |
5241
+ |------|----------------|
5242
+ | \`graph_dependencies\` | "What does UserService depend on?" |
5243
+ | \`graph_impact\` | "What would be affected if I change the User model?" |
5244
+ | \`graph_call_path\` | "How does login() eventually call the database?" |
5245
+ | \`graph_circular_dependencies\` | "Are there any circular dependencies?" |
5246
+ | \`graph_unused_code\` | "Find unused code in this project" |
5247
+
5248
+ ---
5249
+
5250
+ ### \u{1F4C1} Project & Workspace Tools
5251
+
5252
+ | Tool | Example Prompt |
5253
+ |------|----------------|
5254
+ | \`projects_overview\` | "Give me an overview of this project" |
5255
+ | \`projects_index\` | "Re-index this project" |
5256
+ | \`workspaces_overview\` | "Give me a workspace overview" |
5257
+ | \`workspace_associate\` | "Associate this folder with my workspace" |
5258
+ | \`generate_editor_rules\` | "Generate ContextStream rules for this project" |
5259
+
5260
+ ---
5261
+
5262
+ ### Automatic Behavior Guidelines
5263
+
5264
+ **At conversation start:**
5265
+ - Call \`session_init\` to load context (this happens automatically on first tool call)
5266
+ - Review the loaded decisions and memory before responding
5267
+
5268
+ **During the conversation:**
5269
+ - When the user makes a decision \u2192 \`session_capture\` with \`event_type: "decision"\`
5270
+ - When you learn a user preference \u2192 \`session_capture\` with \`event_type: "preference"\`
5271
+ - When discovering important context \u2192 \`session_remember\`
5272
+ - When encountering a bug \u2192 \`session_capture\` with \`event_type: "bug"\`
5273
+ - When a feature is discussed \u2192 \`session_capture\` with \`event_type: "feature"\`
5274
+
5275
+ **When exploring code:**
5276
+ - Use \`search_semantic\` or \`search_hybrid\` for code search
5277
+ - Use \`graph_dependencies\` to understand architecture
5278
+ - Use \`graph_impact\` before making changes
5279
+
5280
+ **At conversation end (if significant work done):**
5281
+ - Summarize and capture key decisions with \`session_capture\`
5282
+
5283
+ ---
5284
+
5285
+ ### Quick Examples
5286
+
5287
+ \`\`\`
5288
+ # Start of conversation - load context
5289
+ session_init()
5290
+
5291
+ # User says "Let's use PostgreSQL for the database"
5292
+ session_capture(event_type="decision", title="Database Choice", content="Decided to use PostgreSQL...")
5293
+
5294
+ # User says "I prefer async/await over callbacks"
5295
+ session_capture(event_type="preference", title="Async Style", content="User prefers async/await...")
5296
+
5297
+ # Need to find related code
5298
+ search_semantic(query="authentication middleware")
5299
+
5300
+ # Check what we discussed before
5301
+ session_recall(query="what did we decide about caching?")
5302
+ \`\`\`
5303
+ `.trim();
5304
+ var TEMPLATES = {
5305
+ windsurf: {
5306
+ filename: ".windsurfrules",
5307
+ description: "Windsurf AI rules",
5308
+ content: `# Windsurf Rules
5309
+ ${CONTEXTSTREAM_RULES}
5310
+ `
5311
+ },
5312
+ cursor: {
5313
+ filename: ".cursorrules",
5314
+ description: "Cursor AI rules",
5315
+ content: `# Cursor Rules
5316
+ ${CONTEXTSTREAM_RULES}
5317
+ `
5318
+ },
5319
+ cline: {
5320
+ filename: ".clinerules",
5321
+ description: "Cline AI rules",
5322
+ content: `# Cline Rules
5323
+ ${CONTEXTSTREAM_RULES}
5324
+ `
5325
+ },
5326
+ claude: {
5327
+ filename: "CLAUDE.md",
5328
+ description: "Claude Code instructions",
5329
+ content: `# Claude Code Instructions
5330
+ ${CONTEXTSTREAM_RULES}
5331
+ `
5332
+ },
5333
+ aider: {
5334
+ filename: ".aider.conf.yml",
5335
+ description: "Aider configuration with system prompt",
5336
+ content: `# Aider Configuration
5337
+ # Note: Aider uses different config format - this adds to the system prompt
5338
+
5339
+ # Add ContextStream guidance to conventions
5340
+ conventions: |
5341
+ ${CONTEXTSTREAM_RULES.split("\n").map((line) => " " + line).join("\n")}
5342
+ `
5343
+ }
5344
+ };
5345
+ function getAvailableEditors() {
5346
+ return Object.keys(TEMPLATES);
5347
+ }
5348
+ function getTemplate(editor) {
5349
+ return TEMPLATES[editor.toLowerCase()] || null;
5350
+ }
5351
+ function generateRuleContent(editor, options) {
5352
+ const template = getTemplate(editor);
5353
+ if (!template) return null;
5354
+ let content = template.content;
5355
+ if (options?.workspaceName || options?.projectName) {
5356
+ const header = `
5357
+ # Workspace: ${options.workspaceName || "Unknown"}
5358
+ ${options.projectName ? `# Project: ${options.projectName}` : ""}
5359
+ ${options.workspaceId ? `# Workspace ID: ${options.workspaceId}` : ""}
5360
+
5361
+ `;
5362
+ content = header + content;
5363
+ }
5364
+ if (options?.additionalRules) {
5365
+ content += "\n\n## Project-Specific Rules\n\n" + options.additionalRules;
5366
+ }
5367
+ return {
5368
+ filename: template.filename,
5369
+ content: content.trim() + "\n"
5370
+ };
5371
+ }
5372
+
5018
5373
  // src/tools.ts
5019
5374
  function formatContent(data) {
5020
5375
  return JSON.stringify(data, null, 2);
@@ -5882,17 +6237,52 @@ Use this to understand how the user likes to work and adapt your responses accor
5882
6237
  description: `Associate a folder/repo with a workspace after user selection.
5883
6238
  Call this after session_init returns status='requires_workspace_selection' and the user has chosen a workspace.
5884
6239
  This persists the selection to .contextstream/config.json so future sessions auto-connect.
5885
- Optionally creates a parent folder mapping (e.g., all repos under /dev/company/* map to the same workspace).`,
6240
+ Optionally creates a parent folder mapping (e.g., all repos under /dev/company/* map to the same workspace).
6241
+ Optionally generates AI editor rules for automatic ContextStream usage.`,
5886
6242
  inputSchema: external_exports.object({
5887
6243
  folder_path: external_exports.string().describe("Absolute path to the folder/repo to associate"),
5888
6244
  workspace_id: external_exports.string().uuid().describe("Workspace ID to associate with"),
5889
6245
  workspace_name: external_exports.string().optional().describe("Workspace name for reference"),
5890
- create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)")
6246
+ create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)"),
6247
+ generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules (.windsurfrules, .cursorrules, etc.) for automatic ContextStream usage")
5891
6248
  })
5892
6249
  },
5893
6250
  async (input) => {
5894
6251
  const result = await client.associateWorkspace(input);
5895
- return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
6252
+ let rulesGenerated = [];
6253
+ if (input.generate_editor_rules) {
6254
+ const fs3 = await import("fs");
6255
+ const path3 = await import("path");
6256
+ for (const editor of getAvailableEditors()) {
6257
+ const rule = generateRuleContent(editor, {
6258
+ workspaceName: input.workspace_name,
6259
+ workspaceId: input.workspace_id
6260
+ });
6261
+ if (rule) {
6262
+ const filePath = path3.join(input.folder_path, rule.filename);
6263
+ try {
6264
+ let existingContent = "";
6265
+ try {
6266
+ existingContent = fs3.readFileSync(filePath, "utf-8");
6267
+ } catch {
6268
+ }
6269
+ if (!existingContent) {
6270
+ fs3.writeFileSync(filePath, rule.content);
6271
+ rulesGenerated.push(rule.filename);
6272
+ } else if (!existingContent.includes("ContextStream Integration")) {
6273
+ fs3.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
6274
+ rulesGenerated.push(rule.filename + " (appended)");
6275
+ }
6276
+ } catch {
6277
+ }
6278
+ }
6279
+ }
6280
+ }
6281
+ const response = {
6282
+ ...result,
6283
+ editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
6284
+ };
6285
+ return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
5896
6286
  }
5897
6287
  );
5898
6288
  registerTool(
@@ -5992,6 +6382,79 @@ Example: "What were the auth decisions?" or "What are my TypeScript preferences?
5992
6382
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
5993
6383
  }
5994
6384
  );
6385
+ registerTool(
6386
+ "generate_editor_rules",
6387
+ {
6388
+ title: "Generate editor AI rules",
6389
+ description: `Generate AI rule files for editors (Windsurf, Cursor, Cline, Claude Code, Aider).
6390
+ These rules instruct the AI to automatically use ContextStream for memory and context.
6391
+ Supported editors: ${getAvailableEditors().join(", ")}`,
6392
+ inputSchema: external_exports.object({
6393
+ folder_path: external_exports.string().describe("Absolute path to the project folder"),
6394
+ editors: external_exports.array(external_exports.enum(["windsurf", "cursor", "cline", "claude", "aider", "all"])).optional().describe("Which editors to generate rules for. Defaults to all."),
6395
+ workspace_name: external_exports.string().optional().describe("Workspace name to include in rules"),
6396
+ workspace_id: external_exports.string().uuid().optional().describe("Workspace ID to include in rules"),
6397
+ project_name: external_exports.string().optional().describe("Project name to include in rules"),
6398
+ additional_rules: external_exports.string().optional().describe("Additional project-specific rules to append"),
6399
+ dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
6400
+ })
6401
+ },
6402
+ async (input) => {
6403
+ const fs3 = await import("fs");
6404
+ const path3 = await import("path");
6405
+ const editors = input.editors?.includes("all") || !input.editors ? getAvailableEditors() : input.editors.filter((e) => e !== "all");
6406
+ const results = [];
6407
+ for (const editor of editors) {
6408
+ const rule = generateRuleContent(editor, {
6409
+ workspaceName: input.workspace_name,
6410
+ workspaceId: input.workspace_id,
6411
+ projectName: input.project_name,
6412
+ additionalRules: input.additional_rules
6413
+ });
6414
+ if (!rule) {
6415
+ results.push({ editor, filename: "", status: "unknown editor" });
6416
+ continue;
6417
+ }
6418
+ const filePath = path3.join(input.folder_path, rule.filename);
6419
+ if (input.dry_run) {
6420
+ results.push({
6421
+ editor,
6422
+ filename: rule.filename,
6423
+ status: "dry run - would create",
6424
+ content: rule.content
6425
+ });
6426
+ } else {
6427
+ try {
6428
+ let existingContent = "";
6429
+ try {
6430
+ existingContent = fs3.readFileSync(filePath, "utf-8");
6431
+ } catch {
6432
+ }
6433
+ if (existingContent && !existingContent.includes("ContextStream Integration")) {
6434
+ const updatedContent = existingContent + "\n\n" + rule.content;
6435
+ fs3.writeFileSync(filePath, updatedContent);
6436
+ results.push({ editor, filename: rule.filename, status: "appended to existing" });
6437
+ } else {
6438
+ fs3.writeFileSync(filePath, rule.content);
6439
+ results.push({ editor, filename: rule.filename, status: "created" });
6440
+ }
6441
+ } catch (err) {
6442
+ results.push({
6443
+ editor,
6444
+ filename: rule.filename,
6445
+ status: `error: ${err.message}`
6446
+ });
6447
+ }
6448
+ }
6449
+ }
6450
+ const summary = {
6451
+ folder: input.folder_path,
6452
+ results,
6453
+ message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : `Generated ${results.filter((r) => r.status === "created" || r.status.includes("appended")).length} rule files.`
6454
+ };
6455
+ return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
6456
+ }
6457
+ );
5995
6458
  }
5996
6459
 
5997
6460
  // src/resources.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextstream/mcp-server",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "MCP server exposing ContextStream public API - code context, memory, search, and AI tools for developers",
5
5
  "type": "module",
6
6
  "license": "MIT",