@contextstream/mcp-server 0.3.13 → 0.3.15

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/dist/index.js +296 -3
  4. package/package.json +10 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ContextStream
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # ContextStream MCP Server
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/@contextstream%2Fmcp-server.svg)](https://www.npmjs.com/package/@contextstream/mcp-server)
4
+ [![GitHub](https://img.shields.io/github/license/contextstream/mcp-server)](https://github.com/contextstream/mcp-server)
4
5
 
5
6
  ## 🧠 Your AI's Permanent Brain — Across Every Tool, Every Conversation
6
7
 
@@ -364,6 +365,37 @@ Works with **all MCP clients** — no client-side changes required.
364
365
  - **Documentation:** [contextstream.io/docs](https://contextstream.io/docs)
365
366
  - **MCP Setup Guide:** [contextstream.io/docs/mcp](https://contextstream.io/docs/mcp)
366
367
  - **npm:** [@contextstream/mcp-server](https://www.npmjs.com/package/@contextstream/mcp-server)
368
+ - **GitHub:** [contextstream/mcp-server](https://github.com/contextstream/mcp-server)
369
+
370
+ ---
371
+
372
+ ## 🤝 Contributing
373
+
374
+ We welcome contributions! Here's how you can help:
375
+
376
+ 1. **Report bugs** — Open an issue at [GitHub Issues](https://github.com/contextstream/mcp-server/issues)
377
+ 2. **Request features** — Share your ideas in the issues
378
+ 3. **Submit PRs** — Fork the repo and submit pull requests
379
+
380
+ ### Development
381
+
382
+ ```bash
383
+ # Clone the repo
384
+ git clone https://github.com/contextstream/mcp-server.git
385
+ cd mcp-server
386
+
387
+ # Install dependencies
388
+ npm install
389
+
390
+ # Run in development mode
391
+ npm run dev
392
+
393
+ # Build for production
394
+ npm run build
395
+
396
+ # Type check
397
+ npm run typecheck
398
+ ```
367
399
 
368
400
  ---
369
401
 
package/dist/index.js CHANGED
@@ -3837,7 +3837,7 @@ ZodNaN.create = (params) => {
3837
3837
  ...processCreateParams(params)
3838
3838
  });
3839
3839
  };
3840
- var BRAND = Symbol("zod_brand");
3840
+ var BRAND = /* @__PURE__ */ Symbol("zod_brand");
3841
3841
  var ZodBranded = class extends ZodType {
3842
3842
  _parse(input) {
3843
3843
  const { ctx } = this._processInputParams(input);
@@ -4080,6 +4080,9 @@ function loadConfig() {
4080
4080
  return parsed.data;
4081
4081
  }
4082
4082
 
4083
+ // src/client.ts
4084
+ import { randomUUID } from "node:crypto";
4085
+
4083
4086
  // src/http.ts
4084
4087
  var HttpError = class extends Error {
4085
4088
  constructor(status, message, body) {
@@ -4880,7 +4883,7 @@ var ContextStreamClient = class {
4880
4883
  let projectId = params.project_id || this.config.defaultProjectId;
4881
4884
  let workspaceName;
4882
4885
  const context = {
4883
- session_id: params.session_id || crypto.randomUUID(),
4886
+ session_id: params.session_id || randomUUID(),
4884
4887
  initialized_at: (/* @__PURE__ */ new Date()).toISOString()
4885
4888
  };
4886
4889
  const rootPath = ideRoots.length > 0 ? ideRoots[0] : void 0;
@@ -5082,6 +5085,19 @@ var ContextStreamClient = class {
5082
5085
  if (batchedContext.relevant_context) {
5083
5086
  context.relevant_context = batchedContext.relevant_context;
5084
5087
  }
5088
+ try {
5089
+ const lessons = await this.getHighPriorityLessons({
5090
+ workspace_id: workspaceId,
5091
+ project_id: projectId,
5092
+ context_hint: params.context_hint,
5093
+ limit: 5
5094
+ });
5095
+ if (lessons.length > 0) {
5096
+ context.lessons = lessons;
5097
+ context.lessons_warning = `\u26A0\uFE0F ${lessons.length} lesson(s) from past mistakes. Review before making changes.`;
5098
+ }
5099
+ } catch {
5100
+ }
5085
5101
  } catch (e) {
5086
5102
  console.error("[ContextStream] Batched endpoint failed, falling back to individual calls:", e);
5087
5103
  await this._fetchSessionContextFallback(context, workspaceId, projectId, params);
@@ -5155,6 +5171,19 @@ var ContextStreamClient = class {
5155
5171
  } catch {
5156
5172
  }
5157
5173
  }
5174
+ try {
5175
+ const lessons = await this.getHighPriorityLessons({
5176
+ workspace_id: workspaceId,
5177
+ project_id: projectId,
5178
+ context_hint: params.context_hint,
5179
+ limit: 5
5180
+ });
5181
+ if (lessons.length > 0) {
5182
+ context.lessons = lessons;
5183
+ context.lessons_warning = `\u26A0\uFE0F ${lessons.length} lesson(s) from past mistakes. Review before making changes.`;
5184
+ }
5185
+ } catch {
5186
+ }
5158
5187
  }
5159
5188
  /**
5160
5189
  * Associate a folder with a workspace (called after user selects from candidates).
@@ -5233,6 +5262,17 @@ var ContextStreamClient = class {
5233
5262
  apiEventType = "manual_note";
5234
5263
  tags.push(params.event_type);
5235
5264
  break;
5265
+ // Lesson system types - all stored as manual_note with specific tags
5266
+ case "correction":
5267
+ case "lesson":
5268
+ case "warning":
5269
+ case "frustration":
5270
+ apiEventType = "manual_note";
5271
+ tags.push(params.event_type);
5272
+ if (!tags.includes("lesson_system")) {
5273
+ tags.push("lesson_system");
5274
+ }
5275
+ break;
5236
5276
  default:
5237
5277
  apiEventType = "manual_note";
5238
5278
  tags.push(params.event_type);
@@ -5737,6 +5777,25 @@ var ContextStreamClient = class {
5737
5777
  } catch {
5738
5778
  }
5739
5779
  }
5780
+ try {
5781
+ const lessons = await this.getHighPriorityLessons({
5782
+ workspace_id: withDefaults.workspace_id,
5783
+ project_id: withDefaults.project_id,
5784
+ context_hint: params.user_message,
5785
+ limit: 3
5786
+ });
5787
+ for (const lesson of lessons) {
5788
+ const prefix = lesson.severity === "critical" ? "\u26A0\uFE0F " : "";
5789
+ items.push({
5790
+ type: "L",
5791
+ key: "lesson",
5792
+ value: `${prefix}${lesson.title}: ${lesson.prevention.slice(0, 100)}`,
5793
+ relevance: lesson.severity === "critical" ? 1 : 0.9
5794
+ // Lessons are high priority
5795
+ });
5796
+ }
5797
+ } catch {
5798
+ }
5740
5799
  items.sort((a, b) => b.relevance - a.relevance);
5741
5800
  let context;
5742
5801
  let charsUsed = 0;
@@ -5777,6 +5836,51 @@ var ContextStreamClient = class {
5777
5836
  sources_used: items.filter((i) => context.includes(i.value.slice(0, 20))).length
5778
5837
  };
5779
5838
  }
5839
+ /**
5840
+ * Get high-priority lessons that should be surfaced proactively.
5841
+ * Returns critical and high severity lessons for warnings.
5842
+ */
5843
+ async getHighPriorityLessons(params) {
5844
+ const limit = params.limit || 5;
5845
+ try {
5846
+ const searchQuery = params.context_hint ? `${params.context_hint} lesson warning prevention mistake` : "lesson warning prevention mistake critical high";
5847
+ const searchResult = await this.memorySearch({
5848
+ query: searchQuery,
5849
+ workspace_id: params.workspace_id,
5850
+ project_id: params.project_id,
5851
+ limit: limit * 2
5852
+ // Fetch more to filter
5853
+ });
5854
+ if (!searchResult?.results) return [];
5855
+ const lessons = searchResult.results.filter((item) => {
5856
+ const tags = item.metadata?.tags || [];
5857
+ const isLesson = tags.includes("lesson") || tags.includes("lesson_system");
5858
+ if (!isLesson) return false;
5859
+ const severityTag = tags.find((t) => t.startsWith("severity:"));
5860
+ const severity = severityTag?.split(":")[1] || item.metadata?.importance || "medium";
5861
+ return severity === "critical" || severity === "high";
5862
+ }).slice(0, limit).map((item) => {
5863
+ const tags = item.metadata?.tags || [];
5864
+ const severityTag = tags.find((t) => t.startsWith("severity:"));
5865
+ const severity = severityTag?.split(":")[1] || item.metadata?.importance || "medium";
5866
+ const category = tags.find(
5867
+ (t) => ["workflow", "code_quality", "verification", "communication", "project_specific"].includes(t)
5868
+ ) || "unknown";
5869
+ const content = item.content || "";
5870
+ const preventionMatch = content.match(/### Prevention\n([\s\S]*?)(?:\n\n|\n\*\*|$)/);
5871
+ const prevention = preventionMatch?.[1]?.trim() || content.slice(0, 200);
5872
+ return {
5873
+ title: item.title || "Lesson",
5874
+ severity,
5875
+ category,
5876
+ prevention
5877
+ };
5878
+ });
5879
+ return lessons;
5880
+ } catch {
5881
+ return [];
5882
+ }
5883
+ }
5780
5884
  /**
5781
5885
  * Extract keywords from a message for relevance matching
5782
5886
  */
@@ -7078,7 +7182,24 @@ Use this to persist decisions, insights, preferences, or important information.`
7078
7182
  workspace_id: external_exports.string().uuid().optional(),
7079
7183
  project_id: external_exports.string().uuid().optional(),
7080
7184
  session_id: external_exports.string().optional().describe("Session ID to associate with this capture"),
7081
- event_type: external_exports.enum(["conversation", "decision", "insight", "preference", "task", "bug", "feature"]).describe("Type of context being captured"),
7185
+ event_type: external_exports.enum([
7186
+ "conversation",
7187
+ "decision",
7188
+ "insight",
7189
+ "preference",
7190
+ "task",
7191
+ "bug",
7192
+ "feature",
7193
+ // Lesson system types
7194
+ "correction",
7195
+ // User corrected the AI
7196
+ "lesson",
7197
+ // Extracted lesson from correction
7198
+ "warning",
7199
+ // Proactive reminder
7200
+ "frustration"
7201
+ // User expressed frustration
7202
+ ]).describe("Type of context being captured"),
7082
7203
  title: external_exports.string().describe("Brief title for the captured context"),
7083
7204
  content: external_exports.string().describe("Full content/details to capture"),
7084
7205
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
@@ -7112,6 +7233,178 @@ Use this to persist decisions, insights, preferences, or important information.`
7112
7233
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7113
7234
  }
7114
7235
  );
7236
+ registerTool(
7237
+ "session_capture_lesson",
7238
+ {
7239
+ title: "Capture a lesson learned",
7240
+ description: `Capture a lesson learned from a mistake or correction.
7241
+ Use this when the user corrects you, expresses frustration, or points out an error.
7242
+ These lessons are surfaced in future sessions to prevent repeating the same mistakes.
7243
+
7244
+ Example triggers:
7245
+ - User says "No, you should..." or "That's wrong"
7246
+ - User expresses frustration (caps, "COME ON", "WTF")
7247
+ - Code breaks due to a preventable mistake
7248
+
7249
+ The lesson will be tagged with 'lesson' and stored with structured metadata for easy retrieval.`,
7250
+ inputSchema: external_exports.object({
7251
+ workspace_id: external_exports.string().uuid().optional(),
7252
+ project_id: external_exports.string().uuid().optional(),
7253
+ title: external_exports.string().describe('Lesson title - what to remember (e.g., "Always verify assets in git before pushing")'),
7254
+ severity: external_exports.enum(["low", "medium", "high", "critical"]).default("medium").describe("Severity: critical for production issues, high for breaking changes, medium for workflow, low for minor"),
7255
+ category: external_exports.enum(["workflow", "code_quality", "verification", "communication", "project_specific"]).describe("Category of the lesson"),
7256
+ trigger: external_exports.string().describe('What action caused the problem (e.g., "Pushed code referencing images without committing them")'),
7257
+ impact: external_exports.string().describe('What went wrong (e.g., "Production 404 errors - broken landing page")'),
7258
+ prevention: external_exports.string().describe('How to prevent in future (e.g., "Run git status to check untracked files before pushing")'),
7259
+ keywords: external_exports.array(external_exports.string()).optional().describe('Keywords for matching in future contexts (e.g., ["git", "images", "assets", "push"])')
7260
+ })
7261
+ },
7262
+ async (input) => {
7263
+ let workspaceId = input.workspace_id;
7264
+ let projectId = input.project_id;
7265
+ if (!workspaceId && sessionManager) {
7266
+ const ctx = sessionManager.getContext();
7267
+ if (ctx) {
7268
+ workspaceId = ctx.workspace_id;
7269
+ projectId = projectId || ctx.project_id;
7270
+ }
7271
+ }
7272
+ if (!workspaceId) {
7273
+ return {
7274
+ content: [{
7275
+ type: "text",
7276
+ text: "Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
7277
+ }],
7278
+ isError: true
7279
+ };
7280
+ }
7281
+ const lessonContent = [
7282
+ `## ${input.title}`,
7283
+ "",
7284
+ `**Severity:** ${input.severity}`,
7285
+ `**Category:** ${input.category}`,
7286
+ "",
7287
+ "### Trigger",
7288
+ input.trigger,
7289
+ "",
7290
+ "### Impact",
7291
+ input.impact,
7292
+ "",
7293
+ "### Prevention",
7294
+ input.prevention,
7295
+ input.keywords?.length ? `
7296
+ **Keywords:** ${input.keywords.join(", ")}` : ""
7297
+ ].filter(Boolean).join("\n");
7298
+ const result = await client.captureContext({
7299
+ workspace_id: workspaceId,
7300
+ project_id: projectId,
7301
+ event_type: "lesson",
7302
+ title: input.title,
7303
+ content: lessonContent,
7304
+ importance: input.severity,
7305
+ tags: [
7306
+ "lesson",
7307
+ input.category,
7308
+ `severity:${input.severity}`,
7309
+ ...input.keywords || []
7310
+ ]
7311
+ });
7312
+ return {
7313
+ content: [{
7314
+ type: "text",
7315
+ text: `\u2705 Lesson captured: "${input.title}"
7316
+
7317
+ This lesson will be surfaced in future sessions when relevant context is detected.`
7318
+ }],
7319
+ structuredContent: toStructured(result)
7320
+ };
7321
+ }
7322
+ );
7323
+ registerTool(
7324
+ "session_get_lessons",
7325
+ {
7326
+ title: "Get lessons learned",
7327
+ description: `Retrieve lessons learned from past mistakes and corrections.
7328
+ Use this to check for relevant warnings before taking actions that have caused problems before.
7329
+
7330
+ Returns lessons filtered by:
7331
+ - Query: semantic search for relevant lessons
7332
+ - Category: workflow, code_quality, verification, communication, project_specific
7333
+ - Severity: low, medium, high, critical`,
7334
+ inputSchema: external_exports.object({
7335
+ workspace_id: external_exports.string().uuid().optional(),
7336
+ project_id: external_exports.string().uuid().optional(),
7337
+ query: external_exports.string().optional().describe('Search for relevant lessons (e.g., "git push images")'),
7338
+ category: external_exports.enum(["workflow", "code_quality", "verification", "communication", "project_specific"]).optional().describe("Filter by category"),
7339
+ severity: external_exports.enum(["low", "medium", "high", "critical"]).optional().describe("Filter by minimum severity"),
7340
+ limit: external_exports.number().default(10).describe("Maximum lessons to return")
7341
+ })
7342
+ },
7343
+ async (input) => {
7344
+ let workspaceId = input.workspace_id;
7345
+ let projectId = input.project_id;
7346
+ if (!workspaceId && sessionManager) {
7347
+ const ctx = sessionManager.getContext();
7348
+ if (ctx) {
7349
+ workspaceId = ctx.workspace_id;
7350
+ projectId = projectId || ctx.project_id;
7351
+ }
7352
+ }
7353
+ const searchQuery = input.query ? `${input.query} lesson prevention warning` : "lesson prevention warning mistake";
7354
+ const searchResult = await client.memorySearch({
7355
+ query: searchQuery,
7356
+ workspace_id: workspaceId,
7357
+ project_id: projectId,
7358
+ limit: input.limit * 2
7359
+ // Fetch more to filter
7360
+ });
7361
+ let lessons = (searchResult?.results || []).filter((item) => {
7362
+ const tags = item.metadata?.tags || [];
7363
+ const isLesson = tags.includes("lesson");
7364
+ if (!isLesson) return false;
7365
+ if (input.category && !tags.includes(input.category)) {
7366
+ return false;
7367
+ }
7368
+ if (input.severity) {
7369
+ const severityOrder = ["low", "medium", "high", "critical"];
7370
+ const minSeverityIndex = severityOrder.indexOf(input.severity);
7371
+ const itemSeverity = tags.find((t) => t.startsWith("severity:"))?.split(":")[1] || "medium";
7372
+ const itemSeverityIndex = severityOrder.indexOf(itemSeverity);
7373
+ if (itemSeverityIndex < minSeverityIndex) return false;
7374
+ }
7375
+ return true;
7376
+ }).slice(0, input.limit);
7377
+ if (lessons.length === 0) {
7378
+ return {
7379
+ content: [{ type: "text", text: "No lessons found matching your criteria." }],
7380
+ structuredContent: toStructured({ lessons: [], count: 0 })
7381
+ };
7382
+ }
7383
+ const formattedLessons = lessons.map((lesson, i) => {
7384
+ const tags = lesson.metadata?.tags || [];
7385
+ const severity = tags.find((t) => t.startsWith("severity:"))?.split(":")[1] || "medium";
7386
+ const category = tags.find((t) => ["workflow", "code_quality", "verification", "communication", "project_specific"].includes(t)) || "unknown";
7387
+ const severityEmoji = {
7388
+ low: "\u{1F7E2}",
7389
+ medium: "\u{1F7E1}",
7390
+ high: "\u{1F7E0}",
7391
+ critical: "\u{1F534}"
7392
+ }[severity] || "\u26AA";
7393
+ return `${i + 1}. ${severityEmoji} **${lesson.title}**
7394
+ Category: ${category} | Severity: ${severity}
7395
+ ${lesson.content?.slice(0, 200)}...`;
7396
+ }).join("\n\n");
7397
+ return {
7398
+ content: [{
7399
+ type: "text",
7400
+ text: `\u{1F4DA} Found ${lessons.length} lesson(s):
7401
+
7402
+ ${formattedLessons}`
7403
+ }],
7404
+ structuredContent: toStructured({ lessons, count: lessons.length })
7405
+ };
7406
+ }
7407
+ );
7115
7408
  registerTool(
7116
7409
  "session_smart_search",
7117
7410
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextstream/mcp-server",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
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",
@@ -39,5 +39,13 @@
39
39
  "code-context",
40
40
  "memory",
41
41
  "knowledge-graph"
42
- ]
42
+ ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/contextstream/mcp-server.git"
46
+ },
47
+ "homepage": "https://github.com/contextstream/mcp-server#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/contextstream/mcp-server/issues"
50
+ }
43
51
  }