@contextstream/mcp-server 0.3.17 → 0.3.18

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 (3) hide show
  1. package/README.md +16 -1
  2. package/dist/index.js +107 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -23,7 +23,7 @@ One integration. Every AI editor. Persistent memory that never forgets.
23
23
  <div align="center">
24
24
 
25
25
  <a href="https://contextstream.io">
26
- <img src="https://customer-vtx4jsqwkbsjpv5b.cloudflarestream.com/f083cfa709a679bd72ef48aca6fe0af2/thumbnails/thumbnail.gif?time=2s&height=600" alt="ContextStream Demo - AI that remembers across sessions" width="600" />
26
+ <img src="https://raw.githubusercontent.com/contextstream/mcp-server/main/mcp.gif.gif" alt="ContextStream Demo - AI that remembers across sessions" width="600" />
27
27
  </a>
28
28
 
29
29
  <sub>Your AI remembers decisions, preferences, and context — across sessions and tools.</sub>
@@ -119,6 +119,8 @@ CONTEXTSTREAM_API_KEY = "your_api_key"
119
119
 
120
120
  > Codex expects snake_case `mcp_servers` keys. After editing, fully restart Codex.
121
121
 
122
+ > For workspace-pooled rate limiting (Team/Enterprise), the MCP server sends `X-Workspace-Id` based on the active repo/session (or explicit `workspace_id` in tool calls). You can optionally set `CONTEXTSTREAM_WORKSPACE_ID` as a fallback default, but it’s not required and isn’t a good fit if you frequently switch workspaces.
123
+
122
124
  ### AI Rules Files (Recommended)
123
125
 
124
126
  Adding rules files ensures your AI automatically uses ContextStream for memory on every conversation.
@@ -173,6 +175,19 @@ AI: "You prefer functional React components."
173
175
 
174
176
  ✨ **That's it. Your AI remembers now.**
175
177
 
178
+ ### 4. (Optional) Connect GitHub + Slack for richer context
179
+
180
+ MCP gives your AI memory. Integrations make that memory richer by pulling in PRs, issues, and team conversations.
181
+
182
+ - GitHub App setup + connect flow: https://contextstream.io/docs/integrations/github
183
+ - Slack app setup + connect flow: https://contextstream.io/docs/integrations/slack
184
+
185
+ After you connect, try prompts like:
186
+ ```
187
+ "Search our Slack messages for the decision about rate limiting."
188
+ "What did we decide in GitHub issues about the auth flow?"
189
+ ```
190
+
176
191
  ---
177
192
 
178
193
  ## Beyond Memory: Intelligence That Compounds
package/dist/index.js CHANGED
@@ -4149,6 +4149,8 @@ async function request(config, path3, options = {}) {
4149
4149
  };
4150
4150
  if (apiKey) headers["X-API-Key"] = apiKey;
4151
4151
  if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
4152
+ const workspaceId = options.workspaceId || inferWorkspaceIdFromBody(options.body) || inferWorkspaceIdFromPath(apiPath) || config.defaultWorkspaceId;
4153
+ if (workspaceId) headers["X-Workspace-Id"] = workspaceId;
4152
4154
  const fetchOptions = {
4153
4155
  method: options.method || (options.body ? "POST" : "GET"),
4154
4156
  headers
@@ -4192,8 +4194,12 @@ async function request(config, path3, options = {}) {
4192
4194
  payload = await response.text().catch(() => null);
4193
4195
  }
4194
4196
  if (!response.ok) {
4195
- const message = payload?.message || payload?.error || response.statusText;
4196
- lastError = new HttpError(response.status, message, payload);
4197
+ const rateLimit = parseRateLimitHeaders(response.headers);
4198
+ const enrichedPayload = attachRateLimit(payload, rateLimit);
4199
+ const message = extractErrorMessage(enrichedPayload, response.statusText);
4200
+ lastError = new HttpError(response.status, message, enrichedPayload);
4201
+ const apiCode = extractErrorCode(enrichedPayload);
4202
+ if (apiCode) lastError.code = apiCode;
4197
4203
  if (RETRYABLE_STATUSES.has(response.status) && attempt < maxRetries) {
4198
4204
  const retryAfter = response.headers.get("retry-after");
4199
4205
  const delay = retryAfter ? parseInt(retryAfter, 10) * 1e3 : baseDelay * Math.pow(2, attempt);
@@ -4206,6 +4212,72 @@ async function request(config, path3, options = {}) {
4206
4212
  }
4207
4213
  throw lastError || new HttpError(0, "Request failed after retries");
4208
4214
  }
4215
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4216
+ function isUuid(value) {
4217
+ return typeof value === "string" && UUID_RE.test(value);
4218
+ }
4219
+ function inferWorkspaceIdFromBody(body) {
4220
+ if (!body || typeof body !== "object") return void 0;
4221
+ const maybe = body.workspace_id;
4222
+ return isUuid(maybe) ? maybe : void 0;
4223
+ }
4224
+ function inferWorkspaceIdFromPath(apiPath) {
4225
+ const qIndex = apiPath.indexOf("?");
4226
+ if (qIndex >= 0) {
4227
+ try {
4228
+ const query = apiPath.slice(qIndex + 1);
4229
+ const params = new URLSearchParams(query);
4230
+ const ws = params.get("workspace_id");
4231
+ if (isUuid(ws)) return ws;
4232
+ } catch {
4233
+ }
4234
+ }
4235
+ const match = apiPath.match(
4236
+ /\/(?:workspaces|workspace)\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i
4237
+ );
4238
+ return match?.[1];
4239
+ }
4240
+ function parseRateLimitHeaders(headers) {
4241
+ const limit = headers.get("X-RateLimit-Limit");
4242
+ if (!limit) return null;
4243
+ const retryAfter = headers.get("Retry-After");
4244
+ return {
4245
+ limit: parseInt(limit, 10),
4246
+ remaining: parseInt(headers.get("X-RateLimit-Remaining") || "0", 10),
4247
+ reset: parseInt(headers.get("X-RateLimit-Reset") || "0", 10),
4248
+ scope: headers.get("X-RateLimit-Scope") || "unknown",
4249
+ plan: headers.get("X-RateLimit-Plan") || "unknown",
4250
+ group: headers.get("X-RateLimit-Group") || "default",
4251
+ retryAfter: retryAfter ? parseInt(retryAfter, 10) : void 0
4252
+ };
4253
+ }
4254
+ function attachRateLimit(payload, rateLimit) {
4255
+ if (!rateLimit) return payload;
4256
+ if (payload && typeof payload === "object") {
4257
+ return { ...payload, rate_limit: rateLimit };
4258
+ }
4259
+ return { error: payload, rate_limit: rateLimit };
4260
+ }
4261
+ function extractErrorMessage(payload, fallback) {
4262
+ if (!payload) return fallback;
4263
+ const nested = payload?.error;
4264
+ if (nested && typeof nested === "object" && typeof nested.message === "string") {
4265
+ return nested.message;
4266
+ }
4267
+ if (typeof payload.message === "string") return payload.message;
4268
+ if (typeof payload.error === "string") return payload.error;
4269
+ if (typeof payload.detail === "string") return payload.detail;
4270
+ return fallback;
4271
+ }
4272
+ function extractErrorCode(payload) {
4273
+ if (!payload) return null;
4274
+ const nested = payload?.error;
4275
+ if (nested && typeof nested === "object" && typeof nested.code === "string" && nested.code.trim()) {
4276
+ return nested.code.trim();
4277
+ }
4278
+ if (typeof payload.code === "string" && payload.code.trim()) return payload.code.trim();
4279
+ return null;
4280
+ }
4209
4281
 
4210
4282
  // src/files.ts
4211
4283
  import * as fs from "fs";
@@ -4558,6 +4630,30 @@ var ContextStreamClient = class {
4558
4630
  constructor(config) {
4559
4631
  this.config = config;
4560
4632
  }
4633
+ /**
4634
+ * Update the client's default workspace/project IDs at runtime.
4635
+ *
4636
+ * This is useful for multi-workspace users: once a session is initialized
4637
+ * (via repo mapping or explicit session_init), the MCP server can treat that
4638
+ * workspace as the default for subsequent calls that don't explicitly include
4639
+ * `workspace_id` in the request payload/path/query.
4640
+ */
4641
+ setDefaults(input) {
4642
+ if (input.workspace_id) {
4643
+ try {
4644
+ uuidSchema.parse(input.workspace_id);
4645
+ this.config.defaultWorkspaceId = input.workspace_id;
4646
+ } catch {
4647
+ }
4648
+ }
4649
+ if (input.project_id) {
4650
+ try {
4651
+ uuidSchema.parse(input.project_id);
4652
+ this.config.defaultProjectId = input.project_id;
4653
+ } catch {
4654
+ }
4655
+ }
4656
+ }
4561
4657
  withDefaults(input) {
4562
4658
  const { defaultWorkspaceId, defaultProjectId } = this.config;
4563
4659
  return {
@@ -8246,6 +8342,11 @@ var SessionManager = class {
8246
8342
  markInitialized(context) {
8247
8343
  this.initialized = true;
8248
8344
  this.context = context;
8345
+ const workspaceId = typeof context.workspace_id === "string" ? context.workspace_id : void 0;
8346
+ const projectId = typeof context.project_id === "string" ? context.project_id : void 0;
8347
+ if (workspaceId || projectId) {
8348
+ this.client.setDefaults({ workspace_id: workspaceId, project_id: projectId });
8349
+ }
8249
8350
  }
8250
8351
  /**
8251
8352
  * Set the folder path hint (can be passed from tools that know the workspace path)
@@ -8359,6 +8460,10 @@ var SessionManager = class {
8359
8460
  );
8360
8461
  this.initialized = true;
8361
8462
  this.context = context;
8463
+ this.client.setDefaults({
8464
+ workspace_id: typeof context.workspace_id === "string" ? context.workspace_id : void 0,
8465
+ project_id: typeof context.project_id === "string" ? context.project_id : void 0
8466
+ });
8362
8467
  console.error("[ContextStream] Workspace resolved:", context.workspace_name, "(source:", context.workspace_source, ")");
8363
8468
  const summary = this.buildContextSummary(context);
8364
8469
  console.error("[ContextStream] Auto-initialization complete");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextstream/mcp-server",
3
- "version": "0.3.17",
3
+ "version": "0.3.18",
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",