@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.
- package/README.md +16 -1
- package/dist/index.js +107 -2
- 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://
|
|
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
|
|
4196
|
-
|
|
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