@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 +1363 -43
- package/dist/test-server.js +7 -4
- package/package.json +1 -1
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,
|
|
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
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
8382
|
+
`Upgrade: ${upgradeUrl2}`
|
|
8147
8383
|
].join("\n")
|
|
8148
8384
|
);
|
|
8149
8385
|
}
|
|
8150
8386
|
function wrapWithAutoContext(toolName, handler) {
|
|
8151
8387
|
if (!sessionManager) {
|
|
8152
|
-
return
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
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
|
-
|
|
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: ${
|
|
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: ${
|
|
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
|
-
|
|
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", {
|
|
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", {
|
|
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}", {
|
|
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
|
|
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"}`);
|