@contextstream/mcp-server 0.3.4 → 0.3.6
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 +529 -29
- 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 {
|
|
@@ -4495,18 +4595,42 @@ var ContextStreamClient = class {
|
|
|
4495
4595
|
uuidSchema.parse(projectId);
|
|
4496
4596
|
return request(this.config, `/projects/${projectId}/index`, { body: {} });
|
|
4497
4597
|
}
|
|
4498
|
-
// Search
|
|
4598
|
+
// Search - each method adds required search_type and filters fields
|
|
4499
4599
|
searchSemantic(body) {
|
|
4500
|
-
return request(this.config, "/search/semantic", {
|
|
4600
|
+
return request(this.config, "/search/semantic", {
|
|
4601
|
+
body: {
|
|
4602
|
+
...this.withDefaults(body),
|
|
4603
|
+
search_type: "semantic",
|
|
4604
|
+
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
4605
|
+
}
|
|
4606
|
+
});
|
|
4501
4607
|
}
|
|
4502
4608
|
searchHybrid(body) {
|
|
4503
|
-
return request(this.config, "/search/hybrid", {
|
|
4609
|
+
return request(this.config, "/search/hybrid", {
|
|
4610
|
+
body: {
|
|
4611
|
+
...this.withDefaults(body),
|
|
4612
|
+
search_type: "hybrid",
|
|
4613
|
+
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
4614
|
+
}
|
|
4615
|
+
});
|
|
4504
4616
|
}
|
|
4505
4617
|
searchKeyword(body) {
|
|
4506
|
-
return request(this.config, "/search/keyword", {
|
|
4618
|
+
return request(this.config, "/search/keyword", {
|
|
4619
|
+
body: {
|
|
4620
|
+
...this.withDefaults(body),
|
|
4621
|
+
search_type: "keyword",
|
|
4622
|
+
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
4623
|
+
}
|
|
4624
|
+
});
|
|
4507
4625
|
}
|
|
4508
4626
|
searchPattern(body) {
|
|
4509
|
-
return request(this.config, "/search/pattern", {
|
|
4627
|
+
return request(this.config, "/search/pattern", {
|
|
4628
|
+
body: {
|
|
4629
|
+
...this.withDefaults(body),
|
|
4630
|
+
search_type: "pattern",
|
|
4631
|
+
filters: body.workspace_id ? {} : { file_types: [], languages: [], file_paths: [], exclude_paths: [], content_types: [], tags: [] }
|
|
4632
|
+
}
|
|
4633
|
+
});
|
|
4510
4634
|
}
|
|
4511
4635
|
// Memory / Knowledge
|
|
4512
4636
|
createMemoryEvent(body) {
|
|
@@ -4587,14 +4711,24 @@ var ContextStreamClient = class {
|
|
|
4587
4711
|
aiEnhancedContext(body) {
|
|
4588
4712
|
return request(this.config, "/ai/context/enhanced", { body: this.withDefaults(body) });
|
|
4589
4713
|
}
|
|
4590
|
-
// Project extended operations
|
|
4591
|
-
getProject(projectId) {
|
|
4714
|
+
// Project extended operations (with caching)
|
|
4715
|
+
async getProject(projectId) {
|
|
4592
4716
|
uuidSchema.parse(projectId);
|
|
4593
|
-
|
|
4717
|
+
const cacheKey = CacheKeys.project(projectId);
|
|
4718
|
+
const cached = globalCache.get(cacheKey);
|
|
4719
|
+
if (cached) return cached;
|
|
4720
|
+
const result = await request(this.config, `/projects/${projectId}`, { method: "GET" });
|
|
4721
|
+
globalCache.set(cacheKey, result, CacheTTL.PROJECT);
|
|
4722
|
+
return result;
|
|
4594
4723
|
}
|
|
4595
|
-
projectOverview(projectId) {
|
|
4724
|
+
async projectOverview(projectId) {
|
|
4596
4725
|
uuidSchema.parse(projectId);
|
|
4597
|
-
|
|
4726
|
+
const cacheKey = `project_overview:${projectId}`;
|
|
4727
|
+
const cached = globalCache.get(cacheKey);
|
|
4728
|
+
if (cached) return cached;
|
|
4729
|
+
const result = await request(this.config, `/projects/${projectId}/overview`, { method: "GET" });
|
|
4730
|
+
globalCache.set(cacheKey, result, CacheTTL.PROJECT);
|
|
4731
|
+
return result;
|
|
4598
4732
|
}
|
|
4599
4733
|
projectStatistics(projectId) {
|
|
4600
4734
|
uuidSchema.parse(projectId);
|
|
@@ -4618,14 +4752,24 @@ var ContextStreamClient = class {
|
|
|
4618
4752
|
body: { files }
|
|
4619
4753
|
});
|
|
4620
4754
|
}
|
|
4621
|
-
// Workspace extended operations
|
|
4622
|
-
getWorkspace(workspaceId) {
|
|
4755
|
+
// Workspace extended operations (with caching)
|
|
4756
|
+
async getWorkspace(workspaceId) {
|
|
4623
4757
|
uuidSchema.parse(workspaceId);
|
|
4624
|
-
|
|
4758
|
+
const cacheKey = CacheKeys.workspace(workspaceId);
|
|
4759
|
+
const cached = globalCache.get(cacheKey);
|
|
4760
|
+
if (cached) return cached;
|
|
4761
|
+
const result = await request(this.config, `/workspaces/${workspaceId}`, { method: "GET" });
|
|
4762
|
+
globalCache.set(cacheKey, result, CacheTTL.WORKSPACE);
|
|
4763
|
+
return result;
|
|
4625
4764
|
}
|
|
4626
|
-
workspaceOverview(workspaceId) {
|
|
4765
|
+
async workspaceOverview(workspaceId) {
|
|
4627
4766
|
uuidSchema.parse(workspaceId);
|
|
4628
|
-
|
|
4767
|
+
const cacheKey = `workspace_overview:${workspaceId}`;
|
|
4768
|
+
const cached = globalCache.get(cacheKey);
|
|
4769
|
+
if (cached) return cached;
|
|
4770
|
+
const result = await request(this.config, `/workspaces/${workspaceId}/overview`, { method: "GET" });
|
|
4771
|
+
globalCache.set(cacheKey, result, CacheTTL.WORKSPACE);
|
|
4772
|
+
return result;
|
|
4629
4773
|
}
|
|
4630
4774
|
workspaceAnalytics(workspaceId) {
|
|
4631
4775
|
uuidSchema.parse(workspaceId);
|
|
@@ -4833,49 +4977,102 @@ var ContextStreamClient = class {
|
|
|
4833
4977
|
context.ide_roots = ideRoots;
|
|
4834
4978
|
if (workspaceId) {
|
|
4835
4979
|
try {
|
|
4836
|
-
|
|
4837
|
-
|
|
4980
|
+
const batchedContext = await this._fetchSessionContextBatched({
|
|
4981
|
+
workspace_id: workspaceId,
|
|
4982
|
+
project_id: projectId,
|
|
4983
|
+
session_id: context.session_id,
|
|
4984
|
+
context_hint: params.context_hint,
|
|
4985
|
+
include_recent_memory: params.include_recent_memory !== false,
|
|
4986
|
+
include_decisions: params.include_decisions !== false
|
|
4987
|
+
});
|
|
4988
|
+
if (batchedContext.workspace) {
|
|
4989
|
+
context.workspace = batchedContext.workspace;
|
|
4990
|
+
}
|
|
4991
|
+
if (batchedContext.project) {
|
|
4992
|
+
context.project = batchedContext.project;
|
|
4993
|
+
}
|
|
4994
|
+
if (batchedContext.recent_memory) {
|
|
4995
|
+
context.recent_memory = { items: batchedContext.recent_memory };
|
|
4996
|
+
}
|
|
4997
|
+
if (batchedContext.recent_decisions) {
|
|
4998
|
+
context.recent_decisions = { items: batchedContext.recent_decisions };
|
|
4999
|
+
}
|
|
5000
|
+
if (batchedContext.relevant_context) {
|
|
5001
|
+
context.relevant_context = batchedContext.relevant_context;
|
|
5002
|
+
}
|
|
5003
|
+
} catch (e) {
|
|
5004
|
+
console.error("[ContextStream] Batched endpoint failed, falling back to individual calls:", e);
|
|
5005
|
+
await this._fetchSessionContextFallback(context, workspaceId, projectId, params);
|
|
4838
5006
|
}
|
|
4839
5007
|
}
|
|
5008
|
+
return context;
|
|
5009
|
+
}
|
|
5010
|
+
/**
|
|
5011
|
+
* Fetch session context using the batched /session/init endpoint.
|
|
5012
|
+
* This is much faster than making 5-6 individual API calls.
|
|
5013
|
+
*/
|
|
5014
|
+
async _fetchSessionContextBatched(params) {
|
|
5015
|
+
const cacheKey = CacheKeys.sessionInit(params.workspace_id, params.project_id);
|
|
5016
|
+
const cached = globalCache.get(cacheKey);
|
|
5017
|
+
if (cached) {
|
|
5018
|
+
console.error("[ContextStream] Session context cache HIT");
|
|
5019
|
+
return cached;
|
|
5020
|
+
}
|
|
5021
|
+
const result = await request(this.config, "/session/init", {
|
|
5022
|
+
body: {
|
|
5023
|
+
workspace_id: params.workspace_id,
|
|
5024
|
+
project_id: params.project_id,
|
|
5025
|
+
session_id: params.session_id,
|
|
5026
|
+
include_recent_memory: params.include_recent_memory ?? true,
|
|
5027
|
+
include_decisions: params.include_decisions ?? true
|
|
5028
|
+
}
|
|
5029
|
+
});
|
|
5030
|
+
const contextData = "data" in result && result.data ? result.data : result;
|
|
5031
|
+
globalCache.set(cacheKey, contextData, CacheTTL.SESSION_INIT);
|
|
5032
|
+
return contextData;
|
|
5033
|
+
}
|
|
5034
|
+
/**
|
|
5035
|
+
* Fallback to individual API calls if batched endpoint is unavailable.
|
|
5036
|
+
*/
|
|
5037
|
+
async _fetchSessionContextFallback(context, workspaceId, projectId, params) {
|
|
5038
|
+
try {
|
|
5039
|
+
context.workspace = await this.workspaceOverview(workspaceId);
|
|
5040
|
+
} catch {
|
|
5041
|
+
}
|
|
4840
5042
|
if (projectId) {
|
|
4841
5043
|
try {
|
|
4842
5044
|
context.project = await this.projectOverview(projectId);
|
|
4843
5045
|
} catch {
|
|
4844
5046
|
}
|
|
4845
5047
|
}
|
|
4846
|
-
if (params.include_recent_memory !== false
|
|
5048
|
+
if (params.include_recent_memory !== false) {
|
|
4847
5049
|
try {
|
|
4848
5050
|
context.recent_memory = await this.listMemoryEvents({
|
|
4849
5051
|
workspace_id: workspaceId,
|
|
4850
|
-
// NOTE: Intentionally NOT filtering by project_id
|
|
4851
|
-
// to get workspace-wide context
|
|
4852
5052
|
limit: 10
|
|
4853
5053
|
});
|
|
4854
5054
|
} catch {
|
|
4855
5055
|
}
|
|
4856
5056
|
}
|
|
4857
|
-
if (params.include_decisions !== false
|
|
5057
|
+
if (params.include_decisions !== false) {
|
|
4858
5058
|
try {
|
|
4859
5059
|
context.recent_decisions = await this.memoryDecisions({
|
|
4860
5060
|
workspace_id: workspaceId,
|
|
4861
|
-
// NOTE: Intentionally NOT filtering by project_id
|
|
4862
5061
|
limit: 5
|
|
4863
5062
|
});
|
|
4864
5063
|
} catch {
|
|
4865
5064
|
}
|
|
4866
5065
|
}
|
|
4867
|
-
if (params.context_hint
|
|
5066
|
+
if (params.context_hint) {
|
|
4868
5067
|
try {
|
|
4869
5068
|
context.relevant_context = await this.memorySearch({
|
|
4870
5069
|
query: params.context_hint,
|
|
4871
5070
|
workspace_id: workspaceId,
|
|
4872
|
-
// Search workspace-wide first
|
|
4873
5071
|
limit: 5
|
|
4874
5072
|
});
|
|
4875
5073
|
} catch {
|
|
4876
5074
|
}
|
|
4877
5075
|
}
|
|
4878
|
-
return context;
|
|
4879
5076
|
}
|
|
4880
5077
|
/**
|
|
4881
5078
|
* Associate a folder with a workspace (called after user selects from candidates).
|
|
@@ -5015,6 +5212,188 @@ var ContextStreamClient = class {
|
|
|
5015
5212
|
}
|
|
5016
5213
|
};
|
|
5017
5214
|
|
|
5215
|
+
// src/rules-templates.ts
|
|
5216
|
+
var CONTEXTSTREAM_RULES = `
|
|
5217
|
+
## ContextStream Integration
|
|
5218
|
+
|
|
5219
|
+
You have access to ContextStream MCP tools for persistent memory and context across sessions.
|
|
5220
|
+
|
|
5221
|
+
---
|
|
5222
|
+
|
|
5223
|
+
### \u{1F6A8} ESSENTIAL Tools (Always Use)
|
|
5224
|
+
|
|
5225
|
+
These tools should be called automatically in every conversation:
|
|
5226
|
+
|
|
5227
|
+
| When | Tool | Example Prompt |
|
|
5228
|
+
|------|------|----------------|
|
|
5229
|
+
| **Conversation start** | \`session_init\` | "Load my workspace context" |
|
|
5230
|
+
| **User makes a decision** | \`session_capture\` | "Remember we decided to use PostgreSQL" |
|
|
5231
|
+
| **User states a preference** | \`session_capture\` | "Remember I prefer TypeScript strict mode" |
|
|
5232
|
+
| **Before starting work** | \`session_recall\` | "What did we decide about auth?" |
|
|
5233
|
+
|
|
5234
|
+
---
|
|
5235
|
+
|
|
5236
|
+
### \u{1F4DD} Memory & Context Tools (Use Frequently)
|
|
5237
|
+
|
|
5238
|
+
| Tool | Example Prompt |
|
|
5239
|
+
|------|----------------|
|
|
5240
|
+
| \`session_remember\` | "Remember that the API uses JWT tokens" |
|
|
5241
|
+
| \`session_smart_search\` | "Search for everything about payment processing" |
|
|
5242
|
+
| \`memory_search\` | "Search my notes for caching strategy" |
|
|
5243
|
+
| \`memory_decisions\` | "Show me all past decisions" |
|
|
5244
|
+
| \`memory_timeline\` | "Show me the timeline of this workspace" |
|
|
5245
|
+
| \`memory_summary\` | "Summarize what we've worked on" |
|
|
5246
|
+
|
|
5247
|
+
---
|
|
5248
|
+
|
|
5249
|
+
### \u{1F50D} Code Search Tools (Use When Exploring Code)
|
|
5250
|
+
|
|
5251
|
+
| Tool | Example Prompt |
|
|
5252
|
+
|------|----------------|
|
|
5253
|
+
| \`search_semantic\` | "How does authentication work in this codebase?" |
|
|
5254
|
+
| \`search_hybrid\` | "Find code related to user permissions" |
|
|
5255
|
+
| \`search_keyword\` | "Search for handleSubmit function" |
|
|
5256
|
+
| \`search_pattern\` | "Find all TODO comments" |
|
|
5257
|
+
| \`ai_context\` | "Build context for implementing user roles" |
|
|
5258
|
+
| \`ai_enhanced_context\` | "Give me deep context on the payment system" |
|
|
5259
|
+
|
|
5260
|
+
---
|
|
5261
|
+
|
|
5262
|
+
### \u{1F578}\uFE0F Code Analysis Tools (Use for Architecture)
|
|
5263
|
+
|
|
5264
|
+
| Tool | Example Prompt |
|
|
5265
|
+
|------|----------------|
|
|
5266
|
+
| \`graph_dependencies\` | "What does UserService depend on?" |
|
|
5267
|
+
| \`graph_impact\` | "What would be affected if I change the User model?" |
|
|
5268
|
+
| \`graph_call_path\` | "How does login() eventually call the database?" |
|
|
5269
|
+
| \`graph_circular_dependencies\` | "Are there any circular dependencies?" |
|
|
5270
|
+
| \`graph_unused_code\` | "Find unused code in this project" |
|
|
5271
|
+
|
|
5272
|
+
---
|
|
5273
|
+
|
|
5274
|
+
### \u{1F4C1} Project & Workspace Tools
|
|
5275
|
+
|
|
5276
|
+
| Tool | Example Prompt |
|
|
5277
|
+
|------|----------------|
|
|
5278
|
+
| \`projects_overview\` | "Give me an overview of this project" |
|
|
5279
|
+
| \`projects_index\` | "Re-index this project" |
|
|
5280
|
+
| \`workspaces_overview\` | "Give me a workspace overview" |
|
|
5281
|
+
| \`workspace_associate\` | "Associate this folder with my workspace" |
|
|
5282
|
+
| \`generate_editor_rules\` | "Generate ContextStream rules for this project" |
|
|
5283
|
+
|
|
5284
|
+
---
|
|
5285
|
+
|
|
5286
|
+
### Automatic Behavior Guidelines
|
|
5287
|
+
|
|
5288
|
+
**At conversation start:**
|
|
5289
|
+
- Call \`session_init\` to load context (this happens automatically on first tool call)
|
|
5290
|
+
- Review the loaded decisions and memory before responding
|
|
5291
|
+
|
|
5292
|
+
**During the conversation:**
|
|
5293
|
+
- When the user makes a decision \u2192 \`session_capture\` with \`event_type: "decision"\`
|
|
5294
|
+
- When you learn a user preference \u2192 \`session_capture\` with \`event_type: "preference"\`
|
|
5295
|
+
- When discovering important context \u2192 \`session_remember\`
|
|
5296
|
+
- When encountering a bug \u2192 \`session_capture\` with \`event_type: "bug"\`
|
|
5297
|
+
- When a feature is discussed \u2192 \`session_capture\` with \`event_type: "feature"\`
|
|
5298
|
+
|
|
5299
|
+
**When exploring code:**
|
|
5300
|
+
- Use \`search_semantic\` or \`search_hybrid\` for code search
|
|
5301
|
+
- Use \`graph_dependencies\` to understand architecture
|
|
5302
|
+
- Use \`graph_impact\` before making changes
|
|
5303
|
+
|
|
5304
|
+
**At conversation end (if significant work done):**
|
|
5305
|
+
- Summarize and capture key decisions with \`session_capture\`
|
|
5306
|
+
|
|
5307
|
+
---
|
|
5308
|
+
|
|
5309
|
+
### Quick Examples
|
|
5310
|
+
|
|
5311
|
+
\`\`\`
|
|
5312
|
+
# Start of conversation - load context
|
|
5313
|
+
session_init()
|
|
5314
|
+
|
|
5315
|
+
# User says "Let's use PostgreSQL for the database"
|
|
5316
|
+
session_capture(event_type="decision", title="Database Choice", content="Decided to use PostgreSQL...")
|
|
5317
|
+
|
|
5318
|
+
# User says "I prefer async/await over callbacks"
|
|
5319
|
+
session_capture(event_type="preference", title="Async Style", content="User prefers async/await...")
|
|
5320
|
+
|
|
5321
|
+
# Need to find related code
|
|
5322
|
+
search_semantic(query="authentication middleware")
|
|
5323
|
+
|
|
5324
|
+
# Check what we discussed before
|
|
5325
|
+
session_recall(query="what did we decide about caching?")
|
|
5326
|
+
\`\`\`
|
|
5327
|
+
`.trim();
|
|
5328
|
+
var TEMPLATES = {
|
|
5329
|
+
windsurf: {
|
|
5330
|
+
filename: ".windsurfrules",
|
|
5331
|
+
description: "Windsurf AI rules",
|
|
5332
|
+
content: `# Windsurf Rules
|
|
5333
|
+
${CONTEXTSTREAM_RULES}
|
|
5334
|
+
`
|
|
5335
|
+
},
|
|
5336
|
+
cursor: {
|
|
5337
|
+
filename: ".cursorrules",
|
|
5338
|
+
description: "Cursor AI rules",
|
|
5339
|
+
content: `# Cursor Rules
|
|
5340
|
+
${CONTEXTSTREAM_RULES}
|
|
5341
|
+
`
|
|
5342
|
+
},
|
|
5343
|
+
cline: {
|
|
5344
|
+
filename: ".clinerules",
|
|
5345
|
+
description: "Cline AI rules",
|
|
5346
|
+
content: `# Cline Rules
|
|
5347
|
+
${CONTEXTSTREAM_RULES}
|
|
5348
|
+
`
|
|
5349
|
+
},
|
|
5350
|
+
claude: {
|
|
5351
|
+
filename: "CLAUDE.md",
|
|
5352
|
+
description: "Claude Code instructions",
|
|
5353
|
+
content: `# Claude Code Instructions
|
|
5354
|
+
${CONTEXTSTREAM_RULES}
|
|
5355
|
+
`
|
|
5356
|
+
},
|
|
5357
|
+
aider: {
|
|
5358
|
+
filename: ".aider.conf.yml",
|
|
5359
|
+
description: "Aider configuration with system prompt",
|
|
5360
|
+
content: `# Aider Configuration
|
|
5361
|
+
# Note: Aider uses different config format - this adds to the system prompt
|
|
5362
|
+
|
|
5363
|
+
# Add ContextStream guidance to conventions
|
|
5364
|
+
conventions: |
|
|
5365
|
+
${CONTEXTSTREAM_RULES.split("\n").map((line) => " " + line).join("\n")}
|
|
5366
|
+
`
|
|
5367
|
+
}
|
|
5368
|
+
};
|
|
5369
|
+
function getAvailableEditors() {
|
|
5370
|
+
return Object.keys(TEMPLATES);
|
|
5371
|
+
}
|
|
5372
|
+
function getTemplate(editor) {
|
|
5373
|
+
return TEMPLATES[editor.toLowerCase()] || null;
|
|
5374
|
+
}
|
|
5375
|
+
function generateRuleContent(editor, options) {
|
|
5376
|
+
const template = getTemplate(editor);
|
|
5377
|
+
if (!template) return null;
|
|
5378
|
+
let content = template.content;
|
|
5379
|
+
if (options?.workspaceName || options?.projectName) {
|
|
5380
|
+
const header = `
|
|
5381
|
+
# Workspace: ${options.workspaceName || "Unknown"}
|
|
5382
|
+
${options.projectName ? `# Project: ${options.projectName}` : ""}
|
|
5383
|
+
${options.workspaceId ? `# Workspace ID: ${options.workspaceId}` : ""}
|
|
5384
|
+
|
|
5385
|
+
`;
|
|
5386
|
+
content = header + content;
|
|
5387
|
+
}
|
|
5388
|
+
if (options?.additionalRules) {
|
|
5389
|
+
content += "\n\n## Project-Specific Rules\n\n" + options.additionalRules;
|
|
5390
|
+
}
|
|
5391
|
+
return {
|
|
5392
|
+
filename: template.filename,
|
|
5393
|
+
content: content.trim() + "\n"
|
|
5394
|
+
};
|
|
5395
|
+
}
|
|
5396
|
+
|
|
5018
5397
|
// src/tools.ts
|
|
5019
5398
|
function formatContent(data) {
|
|
5020
5399
|
return JSON.stringify(data, null, 2);
|
|
@@ -5053,10 +5432,23 @@ function registerTools(server, client, sessionManager) {
|
|
|
5053
5432
|
};
|
|
5054
5433
|
}
|
|
5055
5434
|
function registerTool(name, config, handler) {
|
|
5435
|
+
const safeHandler = async (input) => {
|
|
5436
|
+
try {
|
|
5437
|
+
return await handler(input);
|
|
5438
|
+
} catch (error) {
|
|
5439
|
+
const errorMessage = error?.message || String(error);
|
|
5440
|
+
const errorDetails = error?.body || error?.details || null;
|
|
5441
|
+
const errorCode = error?.code || error?.status || "UNKNOWN_ERROR";
|
|
5442
|
+
const serializedError = new Error(
|
|
5443
|
+
`[${errorCode}] ${errorMessage}${errorDetails ? `: ${JSON.stringify(errorDetails)}` : ""}`
|
|
5444
|
+
);
|
|
5445
|
+
throw serializedError;
|
|
5446
|
+
}
|
|
5447
|
+
};
|
|
5056
5448
|
server.registerTool(
|
|
5057
5449
|
name,
|
|
5058
5450
|
config,
|
|
5059
|
-
wrapWithAutoContext(name,
|
|
5451
|
+
wrapWithAutoContext(name, safeHandler)
|
|
5060
5452
|
);
|
|
5061
5453
|
}
|
|
5062
5454
|
registerTool(
|
|
@@ -5882,17 +6274,52 @@ Use this to understand how the user likes to work and adapt your responses accor
|
|
|
5882
6274
|
description: `Associate a folder/repo with a workspace after user selection.
|
|
5883
6275
|
Call this after session_init returns status='requires_workspace_selection' and the user has chosen a workspace.
|
|
5884
6276
|
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)
|
|
6277
|
+
Optionally creates a parent folder mapping (e.g., all repos under /dev/company/* map to the same workspace).
|
|
6278
|
+
Optionally generates AI editor rules for automatic ContextStream usage.`,
|
|
5886
6279
|
inputSchema: external_exports.object({
|
|
5887
6280
|
folder_path: external_exports.string().describe("Absolute path to the folder/repo to associate"),
|
|
5888
6281
|
workspace_id: external_exports.string().uuid().describe("Workspace ID to associate with"),
|
|
5889
6282
|
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)")
|
|
6283
|
+
create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/maker/* -> workspace)"),
|
|
6284
|
+
generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules (.windsurfrules, .cursorrules, etc.) for automatic ContextStream usage")
|
|
5891
6285
|
})
|
|
5892
6286
|
},
|
|
5893
6287
|
async (input) => {
|
|
5894
6288
|
const result = await client.associateWorkspace(input);
|
|
5895
|
-
|
|
6289
|
+
let rulesGenerated = [];
|
|
6290
|
+
if (input.generate_editor_rules) {
|
|
6291
|
+
const fs3 = await import("fs");
|
|
6292
|
+
const path3 = await import("path");
|
|
6293
|
+
for (const editor of getAvailableEditors()) {
|
|
6294
|
+
const rule = generateRuleContent(editor, {
|
|
6295
|
+
workspaceName: input.workspace_name,
|
|
6296
|
+
workspaceId: input.workspace_id
|
|
6297
|
+
});
|
|
6298
|
+
if (rule) {
|
|
6299
|
+
const filePath = path3.join(input.folder_path, rule.filename);
|
|
6300
|
+
try {
|
|
6301
|
+
let existingContent = "";
|
|
6302
|
+
try {
|
|
6303
|
+
existingContent = fs3.readFileSync(filePath, "utf-8");
|
|
6304
|
+
} catch {
|
|
6305
|
+
}
|
|
6306
|
+
if (!existingContent) {
|
|
6307
|
+
fs3.writeFileSync(filePath, rule.content);
|
|
6308
|
+
rulesGenerated.push(rule.filename);
|
|
6309
|
+
} else if (!existingContent.includes("ContextStream Integration")) {
|
|
6310
|
+
fs3.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
|
|
6311
|
+
rulesGenerated.push(rule.filename + " (appended)");
|
|
6312
|
+
}
|
|
6313
|
+
} catch {
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
const response = {
|
|
6319
|
+
...result,
|
|
6320
|
+
editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
|
|
6321
|
+
};
|
|
6322
|
+
return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
|
|
5896
6323
|
}
|
|
5897
6324
|
);
|
|
5898
6325
|
registerTool(
|
|
@@ -5992,6 +6419,79 @@ Example: "What were the auth decisions?" or "What are my TypeScript preferences?
|
|
|
5992
6419
|
return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
|
|
5993
6420
|
}
|
|
5994
6421
|
);
|
|
6422
|
+
registerTool(
|
|
6423
|
+
"generate_editor_rules",
|
|
6424
|
+
{
|
|
6425
|
+
title: "Generate editor AI rules",
|
|
6426
|
+
description: `Generate AI rule files for editors (Windsurf, Cursor, Cline, Claude Code, Aider).
|
|
6427
|
+
These rules instruct the AI to automatically use ContextStream for memory and context.
|
|
6428
|
+
Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
6429
|
+
inputSchema: external_exports.object({
|
|
6430
|
+
folder_path: external_exports.string().describe("Absolute path to the project folder"),
|
|
6431
|
+
editors: external_exports.array(external_exports.enum(["windsurf", "cursor", "cline", "claude", "aider", "all"])).optional().describe("Which editors to generate rules for. Defaults to all."),
|
|
6432
|
+
workspace_name: external_exports.string().optional().describe("Workspace name to include in rules"),
|
|
6433
|
+
workspace_id: external_exports.string().uuid().optional().describe("Workspace ID to include in rules"),
|
|
6434
|
+
project_name: external_exports.string().optional().describe("Project name to include in rules"),
|
|
6435
|
+
additional_rules: external_exports.string().optional().describe("Additional project-specific rules to append"),
|
|
6436
|
+
dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
|
|
6437
|
+
})
|
|
6438
|
+
},
|
|
6439
|
+
async (input) => {
|
|
6440
|
+
const fs3 = await import("fs");
|
|
6441
|
+
const path3 = await import("path");
|
|
6442
|
+
const editors = input.editors?.includes("all") || !input.editors ? getAvailableEditors() : input.editors.filter((e) => e !== "all");
|
|
6443
|
+
const results = [];
|
|
6444
|
+
for (const editor of editors) {
|
|
6445
|
+
const rule = generateRuleContent(editor, {
|
|
6446
|
+
workspaceName: input.workspace_name,
|
|
6447
|
+
workspaceId: input.workspace_id,
|
|
6448
|
+
projectName: input.project_name,
|
|
6449
|
+
additionalRules: input.additional_rules
|
|
6450
|
+
});
|
|
6451
|
+
if (!rule) {
|
|
6452
|
+
results.push({ editor, filename: "", status: "unknown editor" });
|
|
6453
|
+
continue;
|
|
6454
|
+
}
|
|
6455
|
+
const filePath = path3.join(input.folder_path, rule.filename);
|
|
6456
|
+
if (input.dry_run) {
|
|
6457
|
+
results.push({
|
|
6458
|
+
editor,
|
|
6459
|
+
filename: rule.filename,
|
|
6460
|
+
status: "dry run - would create",
|
|
6461
|
+
content: rule.content
|
|
6462
|
+
});
|
|
6463
|
+
} else {
|
|
6464
|
+
try {
|
|
6465
|
+
let existingContent = "";
|
|
6466
|
+
try {
|
|
6467
|
+
existingContent = fs3.readFileSync(filePath, "utf-8");
|
|
6468
|
+
} catch {
|
|
6469
|
+
}
|
|
6470
|
+
if (existingContent && !existingContent.includes("ContextStream Integration")) {
|
|
6471
|
+
const updatedContent = existingContent + "\n\n" + rule.content;
|
|
6472
|
+
fs3.writeFileSync(filePath, updatedContent);
|
|
6473
|
+
results.push({ editor, filename: rule.filename, status: "appended to existing" });
|
|
6474
|
+
} else {
|
|
6475
|
+
fs3.writeFileSync(filePath, rule.content);
|
|
6476
|
+
results.push({ editor, filename: rule.filename, status: "created" });
|
|
6477
|
+
}
|
|
6478
|
+
} catch (err) {
|
|
6479
|
+
results.push({
|
|
6480
|
+
editor,
|
|
6481
|
+
filename: rule.filename,
|
|
6482
|
+
status: `error: ${err.message}`
|
|
6483
|
+
});
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
const summary = {
|
|
6488
|
+
folder: input.folder_path,
|
|
6489
|
+
results,
|
|
6490
|
+
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.`
|
|
6491
|
+
};
|
|
6492
|
+
return { content: [{ type: "text", text: formatContent(summary) }], structuredContent: toStructured(summary) };
|
|
6493
|
+
}
|
|
6494
|
+
);
|
|
5995
6495
|
}
|
|
5996
6496
|
|
|
5997
6497
|
// src/resources.ts
|
package/package.json
CHANGED