@contextstream/mcp-server 0.3.62 → 0.3.64

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 CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/index.ts
9
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
10
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
11
 
12
12
  // node_modules/zod/v3/external.js
@@ -4057,16 +4057,19 @@ var configSchema = external_exports.object({
4057
4057
  jwt: external_exports.string().min(1).optional(),
4058
4058
  defaultWorkspaceId: external_exports.string().uuid().optional(),
4059
4059
  defaultProjectId: external_exports.string().uuid().optional(),
4060
- userAgent: external_exports.string().default("contextstream-mcp/0.1.0")
4060
+ userAgent: external_exports.string().default("contextstream-mcp/0.1.0"),
4061
+ allowHeaderAuth: external_exports.boolean().optional()
4061
4062
  });
4062
4063
  function loadConfig() {
4064
+ const allowHeaderAuth = process.env.CONTEXTSTREAM_ALLOW_HEADER_AUTH === "1" || process.env.CONTEXTSTREAM_ALLOW_HEADER_AUTH === "true" || process.env.CONTEXTSTREAM_ALLOW_HEADER_AUTH === "yes";
4063
4065
  const parsed = configSchema.safeParse({
4064
4066
  apiUrl: process.env.CONTEXTSTREAM_API_URL,
4065
4067
  apiKey: process.env.CONTEXTSTREAM_API_KEY,
4066
4068
  jwt: process.env.CONTEXTSTREAM_JWT,
4067
4069
  defaultWorkspaceId: process.env.CONTEXTSTREAM_WORKSPACE_ID,
4068
4070
  defaultProjectId: process.env.CONTEXTSTREAM_PROJECT_ID,
4069
- userAgent: process.env.CONTEXTSTREAM_USER_AGENT
4071
+ userAgent: process.env.CONTEXTSTREAM_USER_AGENT,
4072
+ allowHeaderAuth
4070
4073
  });
4071
4074
  if (!parsed.success) {
4072
4075
  const missing = parsed.error.errors.map((e) => e.path.join(".")).join(", ");
@@ -4074,8 +4077,8 @@ function loadConfig() {
4074
4077
  `Invalid configuration. Set CONTEXTSTREAM_API_URL (and API key or JWT). Missing/invalid: ${missing}`
4075
4078
  );
4076
4079
  }
4077
- if (!parsed.data.apiKey && !parsed.data.jwt) {
4078
- throw new Error("Set CONTEXTSTREAM_API_KEY or CONTEXTSTREAM_JWT for authentication.");
4080
+ if (!parsed.data.apiKey && !parsed.data.jwt && !parsed.data.allowHeaderAuth) {
4081
+ throw new Error("Set CONTEXTSTREAM_API_KEY or CONTEXTSTREAM_JWT for authentication (or CONTEXTSTREAM_ALLOW_HEADER_AUTH=true for header-based auth).");
4079
4082
  }
4080
4083
  return parsed.data;
4081
4084
  }
@@ -4084,6 +4087,16 @@ function loadConfig() {
4084
4087
  import { randomUUID } from "node:crypto";
4085
4088
  import * as path3 from "node:path";
4086
4089
 
4090
+ // src/auth-context.ts
4091
+ import { AsyncLocalStorage } from "node:async_hooks";
4092
+ var authContext = new AsyncLocalStorage();
4093
+ function runWithAuthOverride(override, fn) {
4094
+ return authContext.run(override, fn);
4095
+ }
4096
+ function getAuthOverride() {
4097
+ return authContext.getStore() ?? null;
4098
+ }
4099
+
4087
4100
  // src/http.ts
4088
4101
  var HttpError = class extends Error {
4089
4102
  constructor(status, message, body) {
@@ -4139,7 +4152,10 @@ async function sleep(ms) {
4139
4152
  return new Promise((resolve3) => setTimeout(resolve3, ms));
4140
4153
  }
4141
4154
  async function request(config, path7, options = {}) {
4142
- const { apiUrl, apiKey, jwt, userAgent } = config;
4155
+ const { apiUrl, userAgent } = config;
4156
+ const authOverride = getAuthOverride();
4157
+ const apiKey = authOverride?.apiKey ?? config.apiKey;
4158
+ const jwt = authOverride?.jwt ?? config.jwt;
4143
4159
  const apiPath = path7.startsWith("/api/") ? path7 : `/api/v1${path7}`;
4144
4160
  const url = `${apiUrl.replace(/\/$/, "")}${apiPath}`;
4145
4161
  const maxRetries = options.retries ?? MAX_RETRIES;
@@ -4151,7 +4167,7 @@ async function request(config, path7, options = {}) {
4151
4167
  };
4152
4168
  if (apiKey) headers["X-API-Key"] = apiKey;
4153
4169
  if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
4154
- const workspaceId = options.workspaceId || inferWorkspaceIdFromBody(options.body) || inferWorkspaceIdFromPath(apiPath) || config.defaultWorkspaceId;
4170
+ const workspaceId = authOverride?.workspaceId || options.workspaceId || inferWorkspaceIdFromBody(options.body) || inferWorkspaceIdFromPath(apiPath) || config.defaultWorkspaceId;
4155
4171
  if (workspaceId) headers["X-Workspace-Id"] = workspaceId;
4156
4172
  const fetchOptions = {
4157
4173
  method: options.method || (options.body ? "POST" : "GET"),
@@ -7772,6 +7788,226 @@ function getCoreToolsHint() {
7772
7788
  // src/tools.ts
7773
7789
  var LESSON_DEDUP_WINDOW_MS = 2 * 60 * 1e3;
7774
7790
  var recentLessonCaptures = /* @__PURE__ */ new Map();
7791
+ var DEFAULT_PARAM_DESCRIPTIONS = {
7792
+ api_key: "ContextStream API key.",
7793
+ apiKey: "ContextStream API key.",
7794
+ jwt: "ContextStream JWT for authentication.",
7795
+ workspace_id: "Workspace ID (UUID).",
7796
+ workspaceId: "Workspace ID (UUID).",
7797
+ project_id: "Project ID (UUID).",
7798
+ projectId: "Project ID (UUID).",
7799
+ node_id: "Node ID (UUID).",
7800
+ event_id: "Event ID (UUID).",
7801
+ reminder_id: "Reminder ID (UUID).",
7802
+ folder_path: "Absolute path to the local folder.",
7803
+ file_path: "Filesystem path to the file.",
7804
+ path: "Filesystem path.",
7805
+ name: "Name for the resource.",
7806
+ title: "Short descriptive title.",
7807
+ description: "Short description.",
7808
+ content: "Full content/body.",
7809
+ query: "Search query string.",
7810
+ limit: "Maximum number of results to return.",
7811
+ page: "Page number for pagination.",
7812
+ page_size: "Results per page.",
7813
+ include_decisions: "Include related decisions.",
7814
+ include_related: "Include related context.",
7815
+ include_transitive: "Include transitive dependencies.",
7816
+ max_depth: "Maximum traversal depth.",
7817
+ since: "ISO 8601 timestamp to query changes since.",
7818
+ remind_at: "ISO 8601 datetime for the reminder.",
7819
+ priority: "Priority level.",
7820
+ recurrence: "Recurrence pattern (daily, weekly, monthly).",
7821
+ keywords: "Keywords for matching.",
7822
+ overwrite: "Allow overwriting existing files on disk.",
7823
+ write_to_disk: "Write ingested files to disk before indexing.",
7824
+ await_indexing: "Wait for indexing to finish before returning.",
7825
+ auto_index: "Automatically index on creation.",
7826
+ session_id: "Session identifier.",
7827
+ context_hint: "User message used to fetch relevant context.",
7828
+ context: "Context to match relevant reminders."
7829
+ };
7830
+ var WRITE_VERBS = /* @__PURE__ */ new Set([
7831
+ "create",
7832
+ "update",
7833
+ "delete",
7834
+ "ingest",
7835
+ "index",
7836
+ "capture",
7837
+ "remember",
7838
+ "associate",
7839
+ "bootstrap",
7840
+ "snooze",
7841
+ "complete",
7842
+ "dismiss",
7843
+ "generate",
7844
+ "sync",
7845
+ "publish",
7846
+ "set",
7847
+ "add",
7848
+ "remove",
7849
+ "revoke",
7850
+ "upload",
7851
+ "compress",
7852
+ "init"
7853
+ ]);
7854
+ var READ_ONLY_OVERRIDES = /* @__PURE__ */ new Set([
7855
+ "session_tools",
7856
+ "context_smart",
7857
+ "session_summary",
7858
+ "session_recall",
7859
+ "session_get_user_context",
7860
+ "session_get_lessons",
7861
+ "session_smart_search",
7862
+ "session_delta",
7863
+ "projects_list",
7864
+ "projects_get",
7865
+ "projects_overview",
7866
+ "projects_statistics",
7867
+ "projects_files",
7868
+ "projects_index_status",
7869
+ "workspaces_list",
7870
+ "workspaces_get",
7871
+ "memory_search",
7872
+ "memory_decisions",
7873
+ "memory_get_event",
7874
+ "memory_list_events",
7875
+ "memory_list_nodes",
7876
+ "memory_summary",
7877
+ "memory_timeline",
7878
+ "graph_related",
7879
+ "graph_decisions",
7880
+ "graph_path",
7881
+ "graph_dependencies",
7882
+ "graph_call_path",
7883
+ "graph_impact",
7884
+ "graph_circular_dependencies",
7885
+ "graph_unused_code",
7886
+ "search_semantic",
7887
+ "search_hybrid",
7888
+ "search_keyword",
7889
+ "search_pattern",
7890
+ "reminders_list",
7891
+ "reminders_active",
7892
+ "auth_me",
7893
+ "mcp_server_version"
7894
+ ]);
7895
+ var DESTRUCTIVE_VERBS = /* @__PURE__ */ new Set(["delete", "dismiss", "remove", "revoke", "supersede"]);
7896
+ var OPEN_WORLD_PREFIXES = /* @__PURE__ */ new Set(["github", "slack", "integrations"]);
7897
+ function humanizeKey(raw) {
7898
+ const withSpaces = raw.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ");
7899
+ return withSpaces.toLowerCase();
7900
+ }
7901
+ function buildParamDescription(key, path7) {
7902
+ const normalized = key in DEFAULT_PARAM_DESCRIPTIONS ? key : key.toLowerCase();
7903
+ const parent = path7[path7.length - 1];
7904
+ if (parent === "target") {
7905
+ if (key === "id") return "Target identifier (module path, function id, etc.).";
7906
+ if (key === "type") return "Target type (module, file, function, type, variable).";
7907
+ }
7908
+ if (parent === "source") {
7909
+ if (key === "id") return "Source identifier (module path, function id, etc.).";
7910
+ if (key === "type") return "Source type (module, file, function, type, variable).";
7911
+ }
7912
+ if (DEFAULT_PARAM_DESCRIPTIONS[normalized]) {
7913
+ return DEFAULT_PARAM_DESCRIPTIONS[normalized];
7914
+ }
7915
+ if (normalized.endsWith("_id")) {
7916
+ return `ID for the ${humanizeKey(normalized.replace(/_id$/, ""))}.`;
7917
+ }
7918
+ if (normalized.startsWith("include_")) {
7919
+ return `Whether to include ${humanizeKey(normalized.replace("include_", ""))}.`;
7920
+ }
7921
+ if (normalized.startsWith("max_")) {
7922
+ return `Maximum ${humanizeKey(normalized.replace("max_", ""))}.`;
7923
+ }
7924
+ if (normalized.startsWith("min_")) {
7925
+ return `Minimum ${humanizeKey(normalized.replace("min_", ""))}.`;
7926
+ }
7927
+ return `Input parameter: ${humanizeKey(normalized)}.`;
7928
+ }
7929
+ function getDescription(schema) {
7930
+ const def = schema._def;
7931
+ if (def?.description && def.description.trim()) return def.description;
7932
+ return void 0;
7933
+ }
7934
+ function applyParamDescriptions(schema, path7 = []) {
7935
+ if (!(schema instanceof external_exports.ZodObject)) {
7936
+ return schema;
7937
+ }
7938
+ const shape = schema.shape;
7939
+ let changed = false;
7940
+ const nextShape = {};
7941
+ for (const [key, field] of Object.entries(shape)) {
7942
+ let nextField = field;
7943
+ const existingDescription = getDescription(field);
7944
+ if (field instanceof external_exports.ZodObject) {
7945
+ const nested = applyParamDescriptions(field, [...path7, key]);
7946
+ if (nested !== field) {
7947
+ nextField = nested;
7948
+ changed = true;
7949
+ }
7950
+ }
7951
+ if (existingDescription) {
7952
+ if (!getDescription(nextField)) {
7953
+ nextField = nextField.describe(existingDescription);
7954
+ changed = true;
7955
+ }
7956
+ } else {
7957
+ nextField = nextField.describe(buildParamDescription(key, path7));
7958
+ changed = true;
7959
+ }
7960
+ nextShape[key] = nextField;
7961
+ }
7962
+ if (!changed) return schema;
7963
+ let nextSchema = external_exports.object(nextShape);
7964
+ const def = schema._def;
7965
+ if (def?.catchall) nextSchema = nextSchema.catchall(def.catchall);
7966
+ if (def?.unknownKeys === "passthrough") nextSchema = nextSchema.passthrough();
7967
+ if (def?.unknownKeys === "strict") nextSchema = nextSchema.strict();
7968
+ return nextSchema;
7969
+ }
7970
+ function inferToolAnnotations(toolName) {
7971
+ const parts = toolName.split("_");
7972
+ const prefix = parts[0] || toolName;
7973
+ const readOnly = READ_ONLY_OVERRIDES.has(toolName) || !parts.some((part) => WRITE_VERBS.has(part));
7974
+ const destructive = readOnly ? false : parts.some((part) => DESTRUCTIVE_VERBS.has(part));
7975
+ const openWorld = OPEN_WORLD_PREFIXES.has(prefix);
7976
+ return {
7977
+ readOnlyHint: readOnly,
7978
+ destructiveHint: readOnly ? false : destructive,
7979
+ idempotentHint: readOnly,
7980
+ openWorldHint: openWorld
7981
+ };
7982
+ }
7983
+ function normalizeHeaderValue(value) {
7984
+ if (!value) return void 0;
7985
+ return Array.isArray(value) ? value[0] : value;
7986
+ }
7987
+ function resolveAuthOverride(extra) {
7988
+ const token = extra?.authInfo?.token;
7989
+ const tokenType = extra?.authInfo?.extra?.tokenType;
7990
+ const headers = extra?.requestInfo?.headers;
7991
+ const existing = getAuthOverride();
7992
+ const workspaceId = normalizeHeaderValue(headers?.["x-contextstream-workspace-id"]) || normalizeHeaderValue(headers?.["x-workspace-id"]);
7993
+ const projectId = normalizeHeaderValue(headers?.["x-contextstream-project-id"]) || normalizeHeaderValue(headers?.["x-project-id"]);
7994
+ if (token) {
7995
+ if (tokenType === "jwt") {
7996
+ return { jwt: token, workspaceId, projectId };
7997
+ }
7998
+ return { apiKey: token, workspaceId, projectId };
7999
+ }
8000
+ if (existing?.apiKey || existing?.jwt) {
8001
+ return {
8002
+ apiKey: existing.apiKey,
8003
+ jwt: existing.jwt,
8004
+ workspaceId: workspaceId ?? existing.workspaceId,
8005
+ projectId: projectId ?? existing.projectId
8006
+ };
8007
+ }
8008
+ if (!workspaceId && !projectId) return null;
8009
+ return { workspaceId, projectId };
8010
+ }
7775
8011
  var LIGHT_TOOLSET = /* @__PURE__ */ new Set([
7776
8012
  // Core session tools (13)
7777
8013
  "session_init",
@@ -7999,7 +8235,7 @@ function isDuplicateLessonCapture(signature) {
7999
8235
  return false;
8000
8236
  }
8001
8237
  function registerTools(server, client, sessionManager) {
8002
- const upgradeUrl = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
8238
+ const upgradeUrl2 = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
8003
8239
  const toolFilter = resolveToolFilter();
8004
8240
  const toolAllowlist = toolFilter.allowlist;
8005
8241
  if (toolAllowlist) {
@@ -8055,7 +8291,7 @@ function registerTools(server, client, sessionManager) {
8055
8291
  return errorResult(
8056
8292
  [
8057
8293
  `Access denied: \`${toolName}\` requires ContextStream PRO.`,
8058
- `Upgrade: ${upgradeUrl}`
8294
+ `Upgrade: ${upgradeUrl2}`
8059
8295
  ].join("\n")
8060
8296
  );
8061
8297
  }
@@ -8083,7 +8319,7 @@ function registerTools(server, client, sessionManager) {
8083
8319
  [
8084
8320
  `Access denied: \`${toolName}\` is limited to Graph-Lite (module-level, 1-hop queries).`,
8085
8321
  detail,
8086
- `Upgrade to Elite or Team for full graph access: ${upgradeUrl}`
8322
+ `Upgrade to Elite or Team for full graph access: ${upgradeUrl2}`
8087
8323
  ].join("\n")
8088
8324
  );
8089
8325
  }
@@ -8098,7 +8334,7 @@ function registerTools(server, client, sessionManager) {
8098
8334
  [
8099
8335
  `Access denied: \`${toolName}\` requires Elite or Team (Full Graph).`,
8100
8336
  "Pro includes Graph-Lite (module-level dependencies and 1-hop impact only).",
8101
- `Upgrade: ${upgradeUrl}`
8337
+ `Upgrade: ${upgradeUrl2}`
8102
8338
  ].join("\n")
8103
8339
  );
8104
8340
  }
@@ -8143,35 +8379,41 @@ function registerTools(server, client, sessionManager) {
8143
8379
  return errorResult(
8144
8380
  [
8145
8381
  `Access denied: \`${toolName}\` requires ContextStream Pro (Graph-Lite) or Elite/Team (Full Graph).`,
8146
- `Upgrade: ${upgradeUrl}`
8382
+ `Upgrade: ${upgradeUrl2}`
8147
8383
  ].join("\n")
8148
8384
  );
8149
8385
  }
8150
8386
  function wrapWithAutoContext(toolName, handler) {
8151
8387
  if (!sessionManager) {
8152
- return handler;
8153
- }
8154
- return async (input) => {
8155
- const skipAutoInit = toolName === "session_init";
8156
- let contextPrefix = "";
8157
- if (!skipAutoInit) {
8158
- const autoInitResult = await sessionManager.autoInitialize();
8159
- if (autoInitResult) {
8160
- contextPrefix = autoInitResult.contextSummary + "\n\n";
8161
- }
8162
- }
8163
- sessionManager.warnIfContextSmartNotCalled(toolName);
8164
- const result = await handler(input);
8165
- if (contextPrefix && result && typeof result === "object") {
8166
- const r = result;
8167
- if (r.content && r.content.length > 0 && r.content[0].type === "text") {
8168
- r.content[0] = {
8169
- ...r.content[0],
8170
- text: contextPrefix + "--- Tool Response ---\n\n" + r.content[0].text
8171
- };
8388
+ return async (input, extra) => {
8389
+ const authOverride = resolveAuthOverride(extra);
8390
+ return runWithAuthOverride(authOverride, async () => handler(input, extra));
8391
+ };
8392
+ }
8393
+ return async (input, extra) => {
8394
+ const authOverride = resolveAuthOverride(extra);
8395
+ return runWithAuthOverride(authOverride, async () => {
8396
+ const skipAutoInit = toolName === "session_init";
8397
+ let contextPrefix = "";
8398
+ if (!skipAutoInit) {
8399
+ const autoInitResult = await sessionManager.autoInitialize();
8400
+ if (autoInitResult) {
8401
+ contextPrefix = autoInitResult.contextSummary + "\n\n";
8402
+ }
8172
8403
  }
8173
- }
8174
- return result;
8404
+ sessionManager.warnIfContextSmartNotCalled(toolName);
8405
+ const result = await handler(input, extra);
8406
+ if (contextPrefix && result && typeof result === "object") {
8407
+ const r = result;
8408
+ if (r.content && r.content.length > 0 && r.content[0].type === "text") {
8409
+ r.content[0] = {
8410
+ ...r.content[0],
8411
+ text: contextPrefix + "--- Tool Response ---\n\n" + r.content[0].text
8412
+ };
8413
+ }
8414
+ }
8415
+ return result;
8416
+ });
8175
8417
  };
8176
8418
  }
8177
8419
  function registerTool(name, config, handler) {
@@ -8185,20 +8427,28 @@ function registerTools(server, client, sessionManager) {
8185
8427
  title: `${config.title} (${accessLabel})`,
8186
8428
  description: `${config.description}
8187
8429
 
8188
- Access: ${accessLabel}${showUpgrade ? ` (upgrade: ${upgradeUrl})` : ""}`
8430
+ Access: ${accessLabel}${showUpgrade ? ` (upgrade: ${upgradeUrl2})` : ""}`
8431
+ };
8432
+ const annotatedConfig = {
8433
+ ...labeledConfig,
8434
+ inputSchema: labeledConfig.inputSchema ? applyParamDescriptions(labeledConfig.inputSchema) : void 0,
8435
+ annotations: {
8436
+ ...inferToolAnnotations(name),
8437
+ ...labeledConfig.annotations
8438
+ }
8189
8439
  };
8190
- const safeHandler = async (input) => {
8440
+ const safeHandler = async (input, extra) => {
8191
8441
  try {
8192
8442
  const gated = await gateIfProTool(name);
8193
8443
  if (gated) return gated;
8194
- return await handler(input);
8444
+ return await handler(input, extra);
8195
8445
  } catch (error) {
8196
8446
  const errorMessage = error?.message || String(error);
8197
8447
  const errorDetails = error?.body || error?.details || null;
8198
8448
  const errorCode = error?.code || error?.status || "UNKNOWN_ERROR";
8199
8449
  const isPlanLimit = String(errorCode).toUpperCase() === "FORBIDDEN" && String(errorMessage).toLowerCase().includes("plan limit reached");
8200
8450
  const upgradeHint = isPlanLimit ? `
8201
- Upgrade: ${upgradeUrl}` : "";
8451
+ Upgrade: ${upgradeUrl2}` : "";
8202
8452
  const errorPayload = {
8203
8453
  success: false,
8204
8454
  error: {
@@ -8217,7 +8467,7 @@ Upgrade: ${upgradeUrl}` : "";
8217
8467
  };
8218
8468
  server.registerTool(
8219
8469
  name,
8220
- labeledConfig,
8470
+ annotatedConfig,
8221
8471
  wrapWithAutoContext(name, safeHandler)
8222
8472
  );
8223
8473
  }
@@ -11082,10 +11332,33 @@ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
11082
11332
  function wrapText(uri, text) {
11083
11333
  return { contents: [{ uri, text }] };
11084
11334
  }
11335
+ function wrapResource(resource) {
11336
+ return { resources: [resource] };
11337
+ }
11338
+ function extractItems(payload) {
11339
+ if (!payload || typeof payload !== "object") return [];
11340
+ const direct = payload.items;
11341
+ if (Array.isArray(direct)) return direct;
11342
+ const nested = payload.data;
11343
+ if (nested && typeof nested === "object") {
11344
+ const nestedItems = nested.items;
11345
+ if (Array.isArray(nestedItems)) return nestedItems;
11346
+ if (Array.isArray(nested)) return nested;
11347
+ }
11348
+ return [];
11349
+ }
11085
11350
  function registerResources(server, client, apiUrl) {
11086
11351
  server.registerResource(
11087
11352
  "contextstream-openapi",
11088
- new ResourceTemplate("contextstream:openapi", { list: void 0 }),
11353
+ new ResourceTemplate("contextstream:openapi", {
11354
+ list: () => wrapResource({
11355
+ uri: "contextstream:openapi",
11356
+ name: "contextstream-openapi",
11357
+ title: "ContextStream OpenAPI",
11358
+ description: "Machine-readable OpenAPI from the configured API endpoint",
11359
+ mimeType: "application/json"
11360
+ })
11361
+ }),
11089
11362
  {
11090
11363
  title: "ContextStream OpenAPI spec",
11091
11364
  description: "Machine-readable OpenAPI from the configured API endpoint",
@@ -11100,7 +11373,15 @@ function registerResources(server, client, apiUrl) {
11100
11373
  );
11101
11374
  server.registerResource(
11102
11375
  "contextstream-workspaces",
11103
- new ResourceTemplate("contextstream:workspaces", { list: void 0 }),
11376
+ new ResourceTemplate("contextstream:workspaces", {
11377
+ list: () => wrapResource({
11378
+ uri: "contextstream:workspaces",
11379
+ name: "contextstream-workspaces",
11380
+ title: "Workspaces",
11381
+ description: "List of accessible workspaces",
11382
+ mimeType: "application/json"
11383
+ })
11384
+ }),
11104
11385
  { title: "Workspaces", description: "List of accessible workspaces" },
11105
11386
  async () => {
11106
11387
  const data = await client.listWorkspaces();
@@ -11109,7 +11390,36 @@ function registerResources(server, client, apiUrl) {
11109
11390
  );
11110
11391
  server.registerResource(
11111
11392
  "contextstream-projects",
11112
- new ResourceTemplate("contextstream:projects/{workspaceId}", { list: void 0 }),
11393
+ new ResourceTemplate("contextstream:projects/{workspaceId}", {
11394
+ list: async () => {
11395
+ try {
11396
+ const workspaces = await client.listWorkspaces({ page_size: 50 });
11397
+ const items = extractItems(workspaces);
11398
+ return {
11399
+ resources: items.map((workspace) => ({
11400
+ uri: `contextstream:projects/${workspace.id}`,
11401
+ name: `contextstream-projects-${workspace.id}`,
11402
+ title: workspace.name ? `Projects in ${workspace.name}` : "Projects in workspace",
11403
+ description: "Projects in the specified workspace",
11404
+ mimeType: "application/json"
11405
+ }))
11406
+ };
11407
+ } catch {
11408
+ return { resources: [] };
11409
+ }
11410
+ },
11411
+ complete: {
11412
+ workspaceId: async () => {
11413
+ try {
11414
+ const workspaces = await client.listWorkspaces({ page_size: 50 });
11415
+ const items = extractItems(workspaces);
11416
+ return items.map((workspace) => workspace.id);
11417
+ } catch {
11418
+ return [];
11419
+ }
11420
+ }
11421
+ }
11422
+ }),
11113
11423
  { title: "Projects for workspace", description: "Projects in the specified workspace" },
11114
11424
  async (uri, { workspaceId }) => {
11115
11425
  const wsId = Array.isArray(workspaceId) ? workspaceId[0] : workspaceId;
@@ -11119,6 +11429,723 @@ function registerResources(server, client, apiUrl) {
11119
11429
  );
11120
11430
  }
11121
11431
 
11432
+ // src/prompts.ts
11433
+ var ID_NOTES = [
11434
+ "Notes:",
11435
+ "- If ContextStream is not initialized in this conversation, call `session_init` first (omit ids).",
11436
+ "- Do not ask me for `workspace_id`/`project_id` \u2014 use session defaults or IDs returned by `session_init`.",
11437
+ "- Prefer omitting IDs in tool calls when the tool supports defaults."
11438
+ ];
11439
+ var upgradeUrl = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
11440
+ var proPrompts = /* @__PURE__ */ new Set([
11441
+ "build-context",
11442
+ "generate-plan",
11443
+ "generate-tasks",
11444
+ "token-budget-context"
11445
+ ]);
11446
+ function promptAccessLabel(promptName) {
11447
+ return proPrompts.has(promptName) ? "PRO" : "Free";
11448
+ }
11449
+ function registerPrompts(server) {
11450
+ server.registerPrompt(
11451
+ "explore-codebase",
11452
+ {
11453
+ title: `Explore Codebase (${promptAccessLabel("explore-codebase")})`,
11454
+ description: "Get an overview of a project codebase structure and key components"
11455
+ },
11456
+ async () => ({
11457
+ messages: [
11458
+ {
11459
+ role: "user",
11460
+ content: {
11461
+ type: "text",
11462
+ text: [
11463
+ "I want to understand the current codebase.",
11464
+ "",
11465
+ ...ID_NOTES,
11466
+ "",
11467
+ "Please help me by:",
11468
+ "1. Use `projects_overview` to get a project summary (use session defaults; only pass `project_id` if required).",
11469
+ "2. Use `projects_files` to identify key entry points.",
11470
+ "3. If a focus area is clear from our conversation, prioritize it; otherwise ask me what to focus on.",
11471
+ "4. Use `search_semantic` (and optionally `search_hybrid`) to find the most relevant files.",
11472
+ "5. Summarize the architecture, major modules, and where to start editing."
11473
+ ].join("\n")
11474
+ }
11475
+ }
11476
+ ]
11477
+ })
11478
+ );
11479
+ server.registerPrompt(
11480
+ "capture-decision",
11481
+ {
11482
+ title: `Capture Decision (${promptAccessLabel("capture-decision")})`,
11483
+ description: "Document an architectural or technical decision in workspace memory"
11484
+ },
11485
+ async () => ({
11486
+ messages: [
11487
+ {
11488
+ role: "user",
11489
+ content: {
11490
+ type: "text",
11491
+ text: [
11492
+ "Please capture an architectural/technical decision in ContextStream memory.",
11493
+ "",
11494
+ ...ID_NOTES,
11495
+ "",
11496
+ "Instructions:",
11497
+ "- If the decision is already described in this conversation, extract: title, context, decision, consequences/tradeoffs.",
11498
+ "- If anything is missing, ask me 1\u20133 quick questions to fill the gaps.",
11499
+ "- Then call `session_capture` with:",
11500
+ ' - event_type: "decision"',
11501
+ " - title: (short ADR title)",
11502
+ " - content: a well-formatted ADR (Context, Decision, Consequences)",
11503
+ ' - tags: include relevant tags (e.g., "adr", "architecture")',
11504
+ ' - importance: "high"',
11505
+ "",
11506
+ "After capturing, confirm what was saved."
11507
+ ].join("\n")
11508
+ }
11509
+ }
11510
+ ]
11511
+ })
11512
+ );
11513
+ server.registerPrompt(
11514
+ "review-context",
11515
+ {
11516
+ title: `Code Review Context (${promptAccessLabel("review-context")})`,
11517
+ description: "Build context for reviewing code changes"
11518
+ },
11519
+ async () => ({
11520
+ messages: [
11521
+ {
11522
+ role: "user",
11523
+ content: {
11524
+ type: "text",
11525
+ text: [
11526
+ "I need context to review a set of code changes.",
11527
+ "",
11528
+ ...ID_NOTES,
11529
+ "",
11530
+ "First:",
11531
+ "- If file paths and a short change description are not already in this conversation, ask me for them.",
11532
+ "",
11533
+ "Then build review context by:",
11534
+ "1. Using `graph_dependencies` to find what depends on the changed areas.",
11535
+ "2. Using `graph_impact` to assess potential blast radius.",
11536
+ "3. Using `memory_search` to find related decisions/notes.",
11537
+ "4. Using `search_semantic` to find related code patterns.",
11538
+ "",
11539
+ "Provide:",
11540
+ "- What the files/components do",
11541
+ "- What might be affected",
11542
+ "- Relevant prior decisions/lessons",
11543
+ "- Review checklist + risks to focus on"
11544
+ ].join("\n")
11545
+ }
11546
+ }
11547
+ ]
11548
+ })
11549
+ );
11550
+ server.registerPrompt(
11551
+ "investigate-bug",
11552
+ {
11553
+ title: `Investigate Bug (${promptAccessLabel("investigate-bug")})`,
11554
+ description: "Build context for debugging an issue"
11555
+ },
11556
+ async () => ({
11557
+ messages: [
11558
+ {
11559
+ role: "user",
11560
+ content: {
11561
+ type: "text",
11562
+ text: [
11563
+ "I want help investigating a bug.",
11564
+ "",
11565
+ ...ID_NOTES,
11566
+ "",
11567
+ "First:",
11568
+ "- If the error/symptom is not already stated, ask me for the exact error message and what I expected vs what happened.",
11569
+ "- If an affected area/component is not known, ask me where I noticed it.",
11570
+ "",
11571
+ "Then:",
11572
+ "1. Use `search_semantic` to find code related to the error/symptom.",
11573
+ "2. Use `search_pattern` to locate where similar errors are thrown or logged.",
11574
+ "3. If you identify key functions, use `graph_call_path` to trace call flows.",
11575
+ "4. Use `memory_search` to check if we have prior notes/bugs about this area.",
11576
+ "",
11577
+ "Return:",
11578
+ "- Likely origin locations",
11579
+ "- Call flow (if found)",
11580
+ "- Related past context",
11581
+ "- Suggested debugging steps"
11582
+ ].join("\n")
11583
+ }
11584
+ }
11585
+ ]
11586
+ })
11587
+ );
11588
+ server.registerPrompt(
11589
+ "explore-knowledge",
11590
+ {
11591
+ title: `Explore Knowledge Graph (${promptAccessLabel("explore-knowledge")})`,
11592
+ description: "Navigate and understand the knowledge graph for a workspace"
11593
+ },
11594
+ async () => ({
11595
+ messages: [
11596
+ {
11597
+ role: "user",
11598
+ content: {
11599
+ type: "text",
11600
+ text: [
11601
+ "Help me explore the knowledge captured in this workspace.",
11602
+ "",
11603
+ ...ID_NOTES,
11604
+ "",
11605
+ "Approach:",
11606
+ "1. Use `memory_summary` for a high-level overview.",
11607
+ "2. Use `memory_decisions` to see decision history (titles + a few key details).",
11608
+ "3. Use `memory_list_nodes` to see available knowledge nodes.",
11609
+ "4. If a starting topic is clear from the conversation, use `memory_search` for it.",
11610
+ "5. Use `graph_related` on the most relevant nodes to expand connections.",
11611
+ "",
11612
+ "Provide:",
11613
+ "- Key themes and topics",
11614
+ "- Important decisions + rationale",
11615
+ "- Suggested \u201Cnext nodes\u201D to explore"
11616
+ ].join("\n")
11617
+ }
11618
+ }
11619
+ ]
11620
+ })
11621
+ );
11622
+ server.registerPrompt(
11623
+ "onboard-to-project",
11624
+ {
11625
+ title: `Project Onboarding (${promptAccessLabel("onboard-to-project")})`,
11626
+ description: "Generate onboarding context for a new team member"
11627
+ },
11628
+ async () => ({
11629
+ messages: [
11630
+ {
11631
+ role: "user",
11632
+ content: {
11633
+ type: "text",
11634
+ text: [
11635
+ "Create an onboarding guide for a new team member joining this project.",
11636
+ "",
11637
+ ...ID_NOTES,
11638
+ "",
11639
+ "First:",
11640
+ "- If the role is not specified, ask me what role they are onboarding into (backend, frontend, fullstack, etc.).",
11641
+ "",
11642
+ "Gather context:",
11643
+ "1. Use `projects_overview` and `projects_statistics` for project summary.",
11644
+ "2. Use `projects_files` to identify key entry points.",
11645
+ "3. Use `memory_timeline` and `memory_decisions` to understand recent changes and architectural choices.",
11646
+ "4. Use `search_semantic` to find READMEs/docs/setup instructions.",
11647
+ "",
11648
+ "Output:",
11649
+ "- Project overview and purpose",
11650
+ "- Tech stack + architecture map",
11651
+ "- Key files/entry points relevant to the role",
11652
+ "- Important decisions + rationale",
11653
+ "- Recent changes/current focus",
11654
+ "- Step-by-step getting started"
11655
+ ].join("\n")
11656
+ }
11657
+ }
11658
+ ]
11659
+ })
11660
+ );
11661
+ server.registerPrompt(
11662
+ "analyze-refactoring",
11663
+ {
11664
+ title: `Refactoring Analysis (${promptAccessLabel("analyze-refactoring")})`,
11665
+ description: "Analyze a codebase for refactoring opportunities"
11666
+ },
11667
+ async () => ({
11668
+ messages: [
11669
+ {
11670
+ role: "user",
11671
+ content: {
11672
+ type: "text",
11673
+ text: [
11674
+ "Analyze the codebase for refactoring opportunities.",
11675
+ "",
11676
+ ...ID_NOTES,
11677
+ "",
11678
+ "If a target area is obvious from our conversation, focus there; otherwise ask me what area to analyze.",
11679
+ "",
11680
+ "Please investigate:",
11681
+ "1. `graph_circular_dependencies` (circular deps to break)",
11682
+ "2. `graph_unused_code` (dead code to remove)",
11683
+ "3. `search_pattern` (duplication patterns)",
11684
+ "4. `projects_statistics` (high complexity hotspots)",
11685
+ "",
11686
+ "Provide a prioritized list with quick wins vs deeper refactors."
11687
+ ].join("\n")
11688
+ }
11689
+ }
11690
+ ]
11691
+ })
11692
+ );
11693
+ server.registerPrompt(
11694
+ "build-context",
11695
+ {
11696
+ title: `Build LLM Context (${promptAccessLabel("build-context")})`,
11697
+ description: "Build comprehensive context for an LLM task"
11698
+ },
11699
+ async () => ({
11700
+ messages: [
11701
+ {
11702
+ role: "user",
11703
+ content: {
11704
+ type: "text",
11705
+ text: [
11706
+ "Build comprehensive context for the task we are working on.",
11707
+ "",
11708
+ `Access: ${promptAccessLabel("build-context")}${promptAccessLabel("build-context") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
11709
+ "",
11710
+ ...ID_NOTES,
11711
+ "",
11712
+ "First:",
11713
+ "- If the \u201Cquery\u201D is clear from the latest user request, use that.",
11714
+ "- Otherwise ask me: \u201CWhat do you need context for?\u201D",
11715
+ "",
11716
+ "Then:",
11717
+ "- Call `ai_enhanced_context` with include_code=true, include_docs=true, include_memory=true (omit IDs unless required).",
11718
+ "- Synthesize the returned context into a short briefing with links/file paths and key decisions/risks."
11719
+ ].join("\n")
11720
+ }
11721
+ }
11722
+ ]
11723
+ })
11724
+ );
11725
+ server.registerPrompt(
11726
+ "smart-search",
11727
+ {
11728
+ title: `Smart Search (${promptAccessLabel("smart-search")})`,
11729
+ description: "Search across memory, decisions, and code for a query"
11730
+ },
11731
+ async () => ({
11732
+ messages: [
11733
+ {
11734
+ role: "user",
11735
+ content: {
11736
+ type: "text",
11737
+ text: [
11738
+ "Find the most relevant context for what I am asking about.",
11739
+ "",
11740
+ ...ID_NOTES,
11741
+ "",
11742
+ "First:",
11743
+ "- If a query is clear from the conversation, use it.",
11744
+ "- Otherwise ask me what I want to find.",
11745
+ "",
11746
+ "Then:",
11747
+ "1. Use `session_smart_search` for the query.",
11748
+ "2. If results are thin, follow up with `search_hybrid` and `memory_search`.",
11749
+ "3. Return the top results with file paths/links and a short synthesis."
11750
+ ].join("\n")
11751
+ }
11752
+ }
11753
+ ]
11754
+ })
11755
+ );
11756
+ server.registerPrompt(
11757
+ "recall-context",
11758
+ {
11759
+ title: `Recall Context (${promptAccessLabel("recall-context")})`,
11760
+ description: "Retrieve relevant past decisions and memory for a query"
11761
+ },
11762
+ async () => ({
11763
+ messages: [
11764
+ {
11765
+ role: "user",
11766
+ content: {
11767
+ type: "text",
11768
+ text: [
11769
+ "Recall relevant past context (decisions, notes, lessons) for what I am asking about.",
11770
+ "",
11771
+ ...ID_NOTES,
11772
+ "",
11773
+ "First:",
11774
+ "- If a recall query is clear from the conversation, use it.",
11775
+ "- Otherwise ask me what topic I want to recall.",
11776
+ "",
11777
+ "Then:",
11778
+ "- Use `session_recall` with the query (omit IDs unless required).",
11779
+ "- Summarize the key points and any relevant decisions/lessons."
11780
+ ].join("\n")
11781
+ }
11782
+ }
11783
+ ]
11784
+ })
11785
+ );
11786
+ server.registerPrompt(
11787
+ "session-summary",
11788
+ {
11789
+ title: `Session Summary (${promptAccessLabel("session-summary")})`,
11790
+ description: "Get a compact summary of workspace/project context"
11791
+ },
11792
+ async () => ({
11793
+ messages: [
11794
+ {
11795
+ role: "user",
11796
+ content: {
11797
+ type: "text",
11798
+ text: [
11799
+ "Generate a compact, token-efficient summary of the current workspace/project context.",
11800
+ "",
11801
+ ...ID_NOTES,
11802
+ "",
11803
+ "Use `session_summary` (default max_tokens=500 unless I specify otherwise).",
11804
+ "Then list:",
11805
+ "- Top decisions (titles only)",
11806
+ "- Any high-priority lessons to watch for"
11807
+ ].join("\n")
11808
+ }
11809
+ }
11810
+ ]
11811
+ })
11812
+ );
11813
+ server.registerPrompt(
11814
+ "capture-lesson",
11815
+ {
11816
+ title: `Capture Lesson (${promptAccessLabel("capture-lesson")})`,
11817
+ description: "Record a lesson learned from an error or correction"
11818
+ },
11819
+ async () => ({
11820
+ messages: [
11821
+ {
11822
+ role: "user",
11823
+ content: {
11824
+ type: "text",
11825
+ text: [
11826
+ "Capture a lesson learned so it is surfaced in future sessions.",
11827
+ "",
11828
+ ...ID_NOTES,
11829
+ "",
11830
+ "If the lesson details are not fully present in the conversation, ask me for:",
11831
+ "- title (what to remember)",
11832
+ "- severity (low|medium|high|critical, default medium)",
11833
+ "- category (workflow|code_quality|verification|communication|project_specific)",
11834
+ "- trigger (what caused it)",
11835
+ "- impact (what went wrong)",
11836
+ "- prevention (how to prevent it)",
11837
+ "- keywords (optional)",
11838
+ "",
11839
+ "Then call `session_capture_lesson` with those fields and confirm it was saved."
11840
+ ].join("\n")
11841
+ }
11842
+ }
11843
+ ]
11844
+ })
11845
+ );
11846
+ server.registerPrompt(
11847
+ "capture-preference",
11848
+ {
11849
+ title: `Capture Preference (${promptAccessLabel("capture-preference")})`,
11850
+ description: "Save a user preference to memory"
11851
+ },
11852
+ async () => ({
11853
+ messages: [
11854
+ {
11855
+ role: "user",
11856
+ content: {
11857
+ type: "text",
11858
+ text: [
11859
+ "Save a user preference to ContextStream memory.",
11860
+ "",
11861
+ ...ID_NOTES,
11862
+ "",
11863
+ "If the preference is not explicit in the conversation, ask me what to remember.",
11864
+ "",
11865
+ "Then call `session_capture` with:",
11866
+ '- event_type: "preference"',
11867
+ "- title: (short title)",
11868
+ "- content: (preference text)",
11869
+ '- importance: "medium"'
11870
+ ].join("\n")
11871
+ }
11872
+ }
11873
+ ]
11874
+ })
11875
+ );
11876
+ server.registerPrompt(
11877
+ "capture-task",
11878
+ {
11879
+ title: `Capture Task (${promptAccessLabel("capture-task")})`,
11880
+ description: "Capture an action item into memory"
11881
+ },
11882
+ async () => ({
11883
+ messages: [
11884
+ {
11885
+ role: "user",
11886
+ content: {
11887
+ type: "text",
11888
+ text: [
11889
+ "Capture an action item into ContextStream memory.",
11890
+ "",
11891
+ ...ID_NOTES,
11892
+ "",
11893
+ "If the task is not explicit in the conversation, ask me what to capture.",
11894
+ "",
11895
+ "Then call `session_capture` with:",
11896
+ '- event_type: "task"',
11897
+ "- title: (short title)",
11898
+ "- content: (task details)",
11899
+ '- importance: "medium"'
11900
+ ].join("\n")
11901
+ }
11902
+ }
11903
+ ]
11904
+ })
11905
+ );
11906
+ server.registerPrompt(
11907
+ "capture-bug",
11908
+ {
11909
+ title: `Capture Bug (${promptAccessLabel("capture-bug")})`,
11910
+ description: "Capture a bug report into workspace memory"
11911
+ },
11912
+ async () => ({
11913
+ messages: [
11914
+ {
11915
+ role: "user",
11916
+ content: {
11917
+ type: "text",
11918
+ text: [
11919
+ "Capture a bug report in ContextStream memory.",
11920
+ "",
11921
+ ...ID_NOTES,
11922
+ "",
11923
+ "If details are missing, ask me for:",
11924
+ "- title",
11925
+ "- description",
11926
+ "- steps to reproduce (optional)",
11927
+ "- expected behavior (optional)",
11928
+ "- actual behavior (optional)",
11929
+ "",
11930
+ "Then call `session_capture` with:",
11931
+ '- event_type: "bug"',
11932
+ "- title: (bug title)",
11933
+ "- content: a well-formatted bug report (include all provided details)",
11934
+ "- tags: component/area tags",
11935
+ '- importance: "high"'
11936
+ ].join("\n")
11937
+ }
11938
+ }
11939
+ ]
11940
+ })
11941
+ );
11942
+ server.registerPrompt(
11943
+ "capture-feature",
11944
+ {
11945
+ title: `Capture Feature (${promptAccessLabel("capture-feature")})`,
11946
+ description: "Capture a feature request into workspace memory"
11947
+ },
11948
+ async () => ({
11949
+ messages: [
11950
+ {
11951
+ role: "user",
11952
+ content: {
11953
+ type: "text",
11954
+ text: [
11955
+ "Capture a feature request in ContextStream memory.",
11956
+ "",
11957
+ ...ID_NOTES,
11958
+ "",
11959
+ "If details are missing, ask me for:",
11960
+ "- title",
11961
+ "- description",
11962
+ "- rationale (optional)",
11963
+ "- acceptance criteria (optional)",
11964
+ "",
11965
+ "Then call `session_capture` with:",
11966
+ '- event_type: "feature"',
11967
+ "- title: (feature title)",
11968
+ "- content: a well-formatted feature request",
11969
+ "- tags: component/area tags",
11970
+ '- importance: "medium"'
11971
+ ].join("\n")
11972
+ }
11973
+ }
11974
+ ]
11975
+ })
11976
+ );
11977
+ server.registerPrompt(
11978
+ "generate-plan",
11979
+ {
11980
+ title: `Generate Plan (${promptAccessLabel("generate-plan")})`,
11981
+ description: "Generate a development plan from a description"
11982
+ },
11983
+ async () => ({
11984
+ messages: [
11985
+ {
11986
+ role: "user",
11987
+ content: {
11988
+ type: "text",
11989
+ text: [
11990
+ "Generate a development plan for what I am trying to build/fix.",
11991
+ "",
11992
+ `Access: ${promptAccessLabel("generate-plan")}${promptAccessLabel("generate-plan") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
11993
+ "",
11994
+ ...ID_NOTES,
11995
+ "",
11996
+ "Use the most recent user request as the plan description. If unclear, ask me for a one-paragraph description.",
11997
+ "",
11998
+ "Then call `ai_plan` and present the plan as an ordered list with milestones and risks."
11999
+ ].join("\n")
12000
+ }
12001
+ }
12002
+ ]
12003
+ })
12004
+ );
12005
+ server.registerPrompt(
12006
+ "generate-tasks",
12007
+ {
12008
+ title: `Generate Tasks (${promptAccessLabel("generate-tasks")})`,
12009
+ description: "Generate actionable tasks from a plan or description"
12010
+ },
12011
+ async () => ({
12012
+ messages: [
12013
+ {
12014
+ role: "user",
12015
+ content: {
12016
+ type: "text",
12017
+ text: [
12018
+ "Generate actionable tasks for the work we are discussing.",
12019
+ "",
12020
+ `Access: ${promptAccessLabel("generate-tasks")}${promptAccessLabel("generate-tasks") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
12021
+ "",
12022
+ ...ID_NOTES,
12023
+ "",
12024
+ "If a plan_id exists in the conversation, use it. Otherwise use the latest user request as the description.",
12025
+ "If granularity is not specified, default to medium.",
12026
+ "",
12027
+ "Call `ai_tasks` and return a checklist of tasks with acceptance criteria for each."
12028
+ ].join("\n")
12029
+ }
12030
+ }
12031
+ ]
12032
+ })
12033
+ );
12034
+ server.registerPrompt(
12035
+ "token-budget-context",
12036
+ {
12037
+ title: `Token-Budget Context (${promptAccessLabel("token-budget-context")})`,
12038
+ description: "Get the most relevant context that fits within a token budget"
12039
+ },
12040
+ async () => ({
12041
+ messages: [
12042
+ {
12043
+ role: "user",
12044
+ content: {
12045
+ type: "text",
12046
+ text: [
12047
+ "Build the most relevant context that fits within a token budget.",
12048
+ "",
12049
+ `Access: ${promptAccessLabel("token-budget-context")}${promptAccessLabel("token-budget-context") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
12050
+ "",
12051
+ ...ID_NOTES,
12052
+ "",
12053
+ "First:",
12054
+ "- If a query is clear from the conversation, use it; otherwise ask me for a query.",
12055
+ "- If max_tokens is not specified, ask me for a token budget (e.g., 500/1000/2000).",
12056
+ "",
12057
+ "Then call `ai_context_budget` and return the packed context plus a short note about what was included/excluded."
12058
+ ].join("\n")
12059
+ }
12060
+ }
12061
+ ]
12062
+ })
12063
+ );
12064
+ server.registerPrompt(
12065
+ "find-todos",
12066
+ {
12067
+ title: `Find TODOs (${promptAccessLabel("find-todos")})`,
12068
+ description: "Scan the codebase for TODO/FIXME/HACK notes and summarize"
12069
+ },
12070
+ async () => ({
12071
+ messages: [
12072
+ {
12073
+ role: "user",
12074
+ content: {
12075
+ type: "text",
12076
+ text: [
12077
+ "Scan the codebase for TODO/FIXME/HACK notes and summarize them.",
12078
+ "",
12079
+ ...ID_NOTES,
12080
+ "",
12081
+ "Use `search_pattern` with query `TODO|FIXME|HACK` (or a pattern inferred from the conversation).",
12082
+ "Group results by file path, summarize themes, and propose a small prioritized cleanup list."
12083
+ ].join("\n")
12084
+ }
12085
+ }
12086
+ ]
12087
+ })
12088
+ );
12089
+ server.registerPrompt(
12090
+ "generate-editor-rules",
12091
+ {
12092
+ title: `Generate Editor Rules (${promptAccessLabel("generate-editor-rules")})`,
12093
+ description: "Generate ContextStream AI rule files for your editor"
12094
+ },
12095
+ async () => ({
12096
+ messages: [
12097
+ {
12098
+ role: "user",
12099
+ content: {
12100
+ type: "text",
12101
+ text: [
12102
+ "Generate ContextStream AI rule files for my editor.",
12103
+ "",
12104
+ ...ID_NOTES,
12105
+ "",
12106
+ "First:",
12107
+ "- If you can infer the project folder path from the environment/IDE roots, use it.",
12108
+ "- Otherwise ask me for an absolute folder path.",
12109
+ "- Ask which editor(s) (windsurf,cursor,cline,kilo,roo,claude,aider) or default to all.",
12110
+ "",
12111
+ "Then call `generate_editor_rules` and confirm which files were created/updated."
12112
+ ].join("\n")
12113
+ }
12114
+ }
12115
+ ]
12116
+ })
12117
+ );
12118
+ server.registerPrompt(
12119
+ "index-local-repo",
12120
+ {
12121
+ title: `Index Local Repo (${promptAccessLabel("index-local-repo")})`,
12122
+ description: "Ingest local files into ContextStream for indexing/search"
12123
+ },
12124
+ async () => ({
12125
+ messages: [
12126
+ {
12127
+ role: "user",
12128
+ content: {
12129
+ type: "text",
12130
+ text: [
12131
+ "Ingest local files into ContextStream for indexing/search.",
12132
+ "",
12133
+ ...ID_NOTES,
12134
+ "",
12135
+ "First:",
12136
+ "- Ask me for the local directory path to ingest if it is not already specified.",
12137
+ "",
12138
+ "Then:",
12139
+ "- Call `projects_ingest_local` with the path (use session defaults for project, or the `project_id` returned by `session_init`).",
12140
+ "- Explain how to monitor progress via `projects_index_status`."
12141
+ ].join("\n")
12142
+ }
12143
+ }
12144
+ ]
12145
+ })
12146
+ );
12147
+ }
12148
+
11122
12149
  // src/session-manager.ts
11123
12150
  var SessionManager = class {
11124
12151
  constructor(server, client) {
@@ -11407,6 +12434,279 @@ var SessionManager = class {
11407
12434
  }
11408
12435
  };
11409
12436
 
12437
+ // src/http-gateway.ts
12438
+ import { createServer } from "node:http";
12439
+ import { randomUUID as randomUUID2 } from "node:crypto";
12440
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12441
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
12442
+ var HOST = process.env.MCP_HTTP_HOST || "0.0.0.0";
12443
+ var PORT = Number.parseInt(process.env.MCP_HTTP_PORT || "8787", 10);
12444
+ var MCP_PATH = process.env.MCP_HTTP_PATH || "/mcp";
12445
+ var REQUIRE_AUTH = (process.env.MCP_HTTP_REQUIRE_AUTH || "true").toLowerCase() !== "false";
12446
+ var ENABLE_JSON_RESPONSE = (process.env.MCP_HTTP_JSON_RESPONSE || "false").toLowerCase() === "true";
12447
+ var ENABLE_PROMPTS = (process.env.CONTEXTSTREAM_ENABLE_PROMPTS || "true").toLowerCase() !== "false";
12448
+ var WELL_KNOWN_CONFIG_PATH = "/.well-known/mcp-config";
12449
+ var WELL_KNOWN_CARD_PATHS = /* @__PURE__ */ new Set([
12450
+ "/.well-known/mcp.json",
12451
+ "/.well-known/mcp-server.json"
12452
+ ]);
12453
+ var sessions = /* @__PURE__ */ new Map();
12454
+ function normalizeHeaderValue2(value) {
12455
+ if (!value) return void 0;
12456
+ return Array.isArray(value) ? value[0] : value;
12457
+ }
12458
+ function looksLikeJwt(token) {
12459
+ const parts = token.split(".");
12460
+ return parts.length === 3 && parts.every((part) => part.length > 0);
12461
+ }
12462
+ function extractAuthOverride(req, fallback) {
12463
+ const apiKey = normalizeHeaderValue2(req.headers["x-api-key"]) || normalizeHeaderValue2(req.headers["x-contextstream-api-key"]);
12464
+ const jwtHeader = normalizeHeaderValue2(req.headers["x-contextstream-jwt"]);
12465
+ const authHeader = normalizeHeaderValue2(req.headers["authorization"]);
12466
+ let token;
12467
+ if (authHeader) {
12468
+ const [type, ...rest] = authHeader.split(" ");
12469
+ if (type && type.toLowerCase() === "bearer" && rest.length > 0) {
12470
+ token = rest.join(" ").trim();
12471
+ }
12472
+ }
12473
+ const workspaceId = normalizeHeaderValue2(req.headers["x-contextstream-workspace-id"]) || normalizeHeaderValue2(req.headers["x-workspace-id"]);
12474
+ const projectId = normalizeHeaderValue2(req.headers["x-contextstream-project-id"]) || normalizeHeaderValue2(req.headers["x-project-id"]);
12475
+ if (jwtHeader) {
12476
+ return { override: { jwt: jwtHeader, workspaceId, projectId }, tokenType: "jwt" };
12477
+ }
12478
+ if (apiKey) {
12479
+ return { override: { apiKey, workspaceId, projectId }, tokenType: "api_key" };
12480
+ }
12481
+ if (token) {
12482
+ if (looksLikeJwt(token)) {
12483
+ return { override: { jwt: token, workspaceId, projectId }, tokenType: "jwt" };
12484
+ }
12485
+ return { override: { apiKey: token, workspaceId, projectId }, tokenType: "api_key" };
12486
+ }
12487
+ if (workspaceId || projectId) {
12488
+ return { override: { workspaceId, projectId }, tokenType: void 0 };
12489
+ }
12490
+ return { override: fallback, tokenType: void 0 };
12491
+ }
12492
+ function attachAuthInfo(req, token, tokenType) {
12493
+ req.auth = {
12494
+ token,
12495
+ clientId: "contextstream-mcp-http",
12496
+ scopes: [],
12497
+ extra: { tokenType }
12498
+ };
12499
+ }
12500
+ function getBaseUrl(req) {
12501
+ const host = req.headers.host || "localhost";
12502
+ const forwardedProto = normalizeHeaderValue2(req.headers["x-forwarded-proto"]);
12503
+ const proto = forwardedProto || "http";
12504
+ return `${proto}://${host}`;
12505
+ }
12506
+ function buildMcpConfigSchema(baseUrl) {
12507
+ return {
12508
+ $schema: "http://json-schema.org/draft-07/schema#",
12509
+ $id: `${baseUrl}${WELL_KNOWN_CONFIG_PATH}`,
12510
+ title: "ContextStream MCP Session Configuration",
12511
+ description: "Configuration for connecting to the ContextStream MCP HTTP gateway.",
12512
+ "x-query-style": "dot+bracket",
12513
+ type: "object",
12514
+ properties: {
12515
+ apiKey: {
12516
+ type: "string",
12517
+ title: "API Key or JWT",
12518
+ description: "ContextStream API key or JWT used for authentication."
12519
+ },
12520
+ workspaceId: {
12521
+ type: "string",
12522
+ title: "Workspace ID",
12523
+ description: "Optional workspace ID to scope requests.",
12524
+ format: "uuid"
12525
+ },
12526
+ projectId: {
12527
+ type: "string",
12528
+ title: "Project ID",
12529
+ description: "Optional project ID to scope requests.",
12530
+ format: "uuid"
12531
+ }
12532
+ },
12533
+ required: ["apiKey"],
12534
+ additionalProperties: false
12535
+ };
12536
+ }
12537
+ function buildServerCard(baseUrl) {
12538
+ const mcpUrl = `${baseUrl}${MCP_PATH}`;
12539
+ return {
12540
+ $schema: "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
12541
+ name: "io.github.contextstreamio/mcp-server",
12542
+ title: "ContextStream MCP Server",
12543
+ description: "ContextStream MCP server for code context, memory, search, and AI tools.",
12544
+ version: VERSION,
12545
+ websiteUrl: "https://contextstream.io/docs/mcp",
12546
+ repository: {
12547
+ url: "https://github.com/contextstream/mcp-server",
12548
+ source: "github"
12549
+ },
12550
+ icons: [
12551
+ {
12552
+ src: "https://contextstream.io/favicon.svg",
12553
+ mimeType: "image/svg+xml",
12554
+ sizes: ["any"]
12555
+ }
12556
+ ],
12557
+ remotes: [
12558
+ {
12559
+ type: "streamable-http",
12560
+ url: mcpUrl,
12561
+ headers: [
12562
+ {
12563
+ name: "Authorization",
12564
+ value: "Bearer {apiKey}",
12565
+ variables: {
12566
+ apiKey: {
12567
+ description: "ContextStream API key or JWT.",
12568
+ isRequired: true,
12569
+ isSecret: true,
12570
+ placeholder: "cbiq_..."
12571
+ }
12572
+ }
12573
+ },
12574
+ {
12575
+ name: "X-ContextStream-Workspace-Id",
12576
+ value: "{workspaceId}",
12577
+ variables: {
12578
+ workspaceId: {
12579
+ description: "Optional workspace ID.",
12580
+ isRequired: false
12581
+ }
12582
+ }
12583
+ },
12584
+ {
12585
+ name: "X-ContextStream-Project-Id",
12586
+ value: "{projectId}",
12587
+ variables: {
12588
+ projectId: {
12589
+ description: "Optional project ID.",
12590
+ isRequired: false
12591
+ }
12592
+ }
12593
+ }
12594
+ ]
12595
+ }
12596
+ ]
12597
+ };
12598
+ }
12599
+ async function createSession() {
12600
+ const config = loadConfig();
12601
+ const client = new ContextStreamClient(config);
12602
+ const server = new McpServer({
12603
+ name: "contextstream-mcp",
12604
+ version: VERSION
12605
+ });
12606
+ const sessionManager = new SessionManager(server, client);
12607
+ registerTools(server, client, sessionManager);
12608
+ registerResources(server, client, config.apiUrl);
12609
+ if (ENABLE_PROMPTS) {
12610
+ registerPrompts(server);
12611
+ }
12612
+ const transport = new StreamableHTTPServerTransport({
12613
+ sessionIdGenerator: () => randomUUID2(),
12614
+ enableJsonResponse: ENABLE_JSON_RESPONSE,
12615
+ onsessionclosed: (sessionId) => {
12616
+ sessions.delete(sessionId);
12617
+ }
12618
+ });
12619
+ await server.connect(transport);
12620
+ const now = Date.now();
12621
+ return {
12622
+ server,
12623
+ transport,
12624
+ sessionManager,
12625
+ client,
12626
+ authOverride: null,
12627
+ createdAt: now,
12628
+ lastSeenAt: now
12629
+ };
12630
+ }
12631
+ function writeJson(res, status, payload) {
12632
+ res.writeHead(status, { "Content-Type": "application/json" });
12633
+ res.end(JSON.stringify(payload));
12634
+ }
12635
+ async function handleMcpRequest(req, res) {
12636
+ const sessionId = normalizeHeaderValue2(req.headers["mcp-session-id"]);
12637
+ const existingSession = sessionId ? sessions.get(sessionId) : void 0;
12638
+ const { override, tokenType } = extractAuthOverride(req, existingSession?.authOverride ?? null);
12639
+ const token = override?.jwt || override?.apiKey;
12640
+ if (REQUIRE_AUTH && !token) {
12641
+ writeJson(res, 401, { error: "Missing authorization token. Use Authorization: Bearer <API_KEY> or X-API-Key." });
12642
+ return;
12643
+ }
12644
+ if (sessionId && !existingSession) {
12645
+ writeJson(res, 404, { error: "Unknown MCP session. Re-initialize your MCP connection." });
12646
+ return;
12647
+ }
12648
+ let entry = existingSession;
12649
+ if (!entry) {
12650
+ entry = await createSession();
12651
+ }
12652
+ entry.lastSeenAt = Date.now();
12653
+ if (override) {
12654
+ entry.authOverride = override;
12655
+ }
12656
+ if (token && tokenType) {
12657
+ attachAuthInfo(req, token, tokenType);
12658
+ }
12659
+ await runWithAuthOverride(override ?? null, async () => {
12660
+ await entry.transport.handleRequest(req, res);
12661
+ });
12662
+ const newSessionId = entry.transport.sessionId;
12663
+ if (!existingSession && newSessionId) {
12664
+ sessions.set(newSessionId, entry);
12665
+ }
12666
+ }
12667
+ async function runHttpGateway() {
12668
+ const config = loadConfig();
12669
+ console.error(`[ContextStream] MCP HTTP gateway v${VERSION} starting...`);
12670
+ console.error(`[ContextStream] API URL: ${config.apiUrl}`);
12671
+ console.error(`[ContextStream] Auth: ${config.apiKey ? "API Key" : config.jwt ? "JWT" : "Header-based"}`);
12672
+ console.error(`[ContextStream] MCP endpoint: http://${HOST}:${PORT}${MCP_PATH}`);
12673
+ const server = createServer(async (req, res) => {
12674
+ if (!req.url) {
12675
+ writeJson(res, 404, { error: "Not found" });
12676
+ return;
12677
+ }
12678
+ const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
12679
+ if (url.pathname === "/health") {
12680
+ writeJson(res, 200, { status: "ok", version: VERSION });
12681
+ return;
12682
+ }
12683
+ if (url.pathname === WELL_KNOWN_CONFIG_PATH) {
12684
+ writeJson(res, 200, buildMcpConfigSchema(getBaseUrl(req)));
12685
+ return;
12686
+ }
12687
+ if (WELL_KNOWN_CARD_PATHS.has(url.pathname)) {
12688
+ writeJson(res, 200, buildServerCard(getBaseUrl(req)));
12689
+ return;
12690
+ }
12691
+ if (url.pathname !== MCP_PATH) {
12692
+ writeJson(res, 404, { error: "Not found" });
12693
+ return;
12694
+ }
12695
+ if (!["GET", "POST", "DELETE"].includes(req.method || "")) {
12696
+ writeJson(res, 405, { error: "Method not allowed" });
12697
+ return;
12698
+ }
12699
+ try {
12700
+ await handleMcpRequest(req, res);
12701
+ } catch (error) {
12702
+ writeJson(res, 500, { error: error?.message || "Internal server error" });
12703
+ }
12704
+ });
12705
+ server.listen(PORT, HOST, () => {
12706
+ console.error(`[ContextStream] MCP HTTP gateway listening on http://${HOST}:${PORT}${MCP_PATH}`);
12707
+ });
12708
+ }
12709
+
11410
12710
  // src/index.ts
11411
12711
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
11412
12712
  import { homedir as homedir4 } from "os";
@@ -12443,6 +13743,7 @@ Applying to ${projects.length} project(s)...`);
12443
13743
  }
12444
13744
 
12445
13745
  // src/index.ts
13746
+ var ENABLE_PROMPTS2 = (process.env.CONTEXTSTREAM_ENABLE_PROMPTS || "true").toLowerCase() !== "false";
12446
13747
  function showFirstRunMessage() {
12447
13748
  const configDir = join8(homedir4(), ".contextstream");
12448
13749
  const starShownFile = join8(configDir, ".star-shown");
@@ -12475,20 +13776,29 @@ Usage:
12475
13776
  npx -y @contextstream/mcp-server
12476
13777
  contextstream-mcp
12477
13778
  contextstream-mcp setup
13779
+ contextstream-mcp http
12478
13780
 
12479
13781
  Commands:
12480
13782
  setup Interactive onboarding wizard (rules + workspace mapping)
13783
+ http Run HTTP MCP gateway (streamable HTTP transport)
12481
13784
 
12482
13785
  Environment variables:
12483
13786
  CONTEXTSTREAM_API_URL Base API URL (e.g. https://api.contextstream.io)
12484
13787
  CONTEXTSTREAM_API_KEY API key for authentication (or use CONTEXTSTREAM_JWT)
12485
13788
  CONTEXTSTREAM_JWT JWT for authentication (alternative to API key)
13789
+ CONTEXTSTREAM_ALLOW_HEADER_AUTH Allow header-based auth when no API key/JWT is set
12486
13790
  CONTEXTSTREAM_WORKSPACE_ID Optional default workspace ID
12487
13791
  CONTEXTSTREAM_PROJECT_ID Optional default project ID
12488
13792
  CONTEXTSTREAM_TOOLSET Tool mode: light|standard|complete (default: standard)
12489
13793
  CONTEXTSTREAM_TOOL_ALLOWLIST Optional comma-separated tool names to expose (overrides toolset)
12490
13794
  CONTEXTSTREAM_PRO_TOOLS Optional comma-separated PRO tool names (default: AI tools)
12491
13795
  CONTEXTSTREAM_UPGRADE_URL Optional upgrade URL shown for PRO tools on Free plan
13796
+ CONTEXTSTREAM_ENABLE_PROMPTS Enable MCP prompts list (default: true)
13797
+ MCP_HTTP_HOST HTTP gateway host (default: 0.0.0.0)
13798
+ MCP_HTTP_PORT HTTP gateway port (default: 8787)
13799
+ MCP_HTTP_PATH HTTP gateway path (default: /mcp)
13800
+ MCP_HTTP_REQUIRE_AUTH Require auth headers for HTTP gateway (default: true)
13801
+ MCP_HTTP_JSON_RESPONSE Enable JSON responses (default: false)
12492
13802
 
12493
13803
  Examples:
12494
13804
  CONTEXTSTREAM_API_URL="https://api.contextstream.io" \\
@@ -12517,15 +13827,25 @@ async function main() {
12517
13827
  await runSetupWizard(args.slice(1));
12518
13828
  return;
12519
13829
  }
13830
+ if (args[0] === "http") {
13831
+ if (!process.env.CONTEXTSTREAM_API_KEY && !process.env.CONTEXTSTREAM_JWT && !process.env.CONTEXTSTREAM_ALLOW_HEADER_AUTH) {
13832
+ process.env.CONTEXTSTREAM_ALLOW_HEADER_AUTH = "true";
13833
+ }
13834
+ await runHttpGateway();
13835
+ return;
13836
+ }
12520
13837
  const config = loadConfig();
12521
13838
  const client = new ContextStreamClient(config);
12522
- const server = new McpServer({
13839
+ const server = new McpServer2({
12523
13840
  name: "contextstream-mcp",
12524
13841
  version: VERSION
12525
13842
  });
12526
13843
  const sessionManager = new SessionManager(server, client);
12527
13844
  registerTools(server, client, sessionManager);
12528
13845
  registerResources(server, client, config.apiUrl);
13846
+ if (ENABLE_PROMPTS2) {
13847
+ registerPrompts(server);
13848
+ }
12529
13849
  console.error(`ContextStream MCP server v${VERSION} starting...`);
12530
13850
  console.error(`API URL: ${config.apiUrl}`);
12531
13851
  console.error(`Auth: ${config.apiKey ? "API Key" : config.jwt ? "JWT" : "None"}`);