@contextstream/mcp-server 0.3.26 → 0.3.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +20 -10
  2. package/dist/index.js +502 -722
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6282,6 +6282,191 @@ W:${wsHint}
6282
6282
  }
6283
6283
  return matches / keywords.length;
6284
6284
  }
6285
+ // ============================================
6286
+ // Slack Integration Methods
6287
+ // ============================================
6288
+ /**
6289
+ * Get Slack integration statistics and overview
6290
+ */
6291
+ async slackStats(params) {
6292
+ const withDefaults = this.withDefaults(params || {});
6293
+ if (!withDefaults.workspace_id) {
6294
+ throw new Error("workspace_id is required for Slack stats");
6295
+ }
6296
+ const query = new URLSearchParams();
6297
+ if (params?.days) query.set("days", String(params.days));
6298
+ const suffix = query.toString() ? `?${query.toString()}` : "";
6299
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/slack/stats${suffix}`, { method: "GET" });
6300
+ }
6301
+ /**
6302
+ * Get Slack users for a workspace
6303
+ */
6304
+ async slackUsers(params) {
6305
+ const withDefaults = this.withDefaults(params || {});
6306
+ if (!withDefaults.workspace_id) {
6307
+ throw new Error("workspace_id is required for Slack users");
6308
+ }
6309
+ const query = new URLSearchParams();
6310
+ if (params?.page) query.set("page", String(params.page));
6311
+ if (params?.per_page) query.set("per_page", String(params.per_page));
6312
+ const suffix = query.toString() ? `?${query.toString()}` : "";
6313
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/slack/users${suffix}`, { method: "GET" });
6314
+ }
6315
+ /**
6316
+ * Get Slack channels with stats
6317
+ */
6318
+ async slackChannels(params) {
6319
+ const withDefaults = this.withDefaults(params || {});
6320
+ if (!withDefaults.workspace_id) {
6321
+ throw new Error("workspace_id is required for Slack channels");
6322
+ }
6323
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/slack/channels`, { method: "GET" });
6324
+ }
6325
+ /**
6326
+ * Get recent Slack activity feed
6327
+ */
6328
+ async slackActivity(params) {
6329
+ const withDefaults = this.withDefaults(params || {});
6330
+ if (!withDefaults.workspace_id) {
6331
+ throw new Error("workspace_id is required for Slack activity");
6332
+ }
6333
+ const query = new URLSearchParams();
6334
+ if (params?.limit) query.set("limit", String(params.limit));
6335
+ if (params?.offset) query.set("offset", String(params.offset));
6336
+ if (params?.channel_id) query.set("channel_id", params.channel_id);
6337
+ const suffix = query.toString() ? `?${query.toString()}` : "";
6338
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/slack/activity${suffix}`, { method: "GET" });
6339
+ }
6340
+ /**
6341
+ * Get high-engagement Slack discussions
6342
+ */
6343
+ async slackDiscussions(params) {
6344
+ const withDefaults = this.withDefaults(params || {});
6345
+ if (!withDefaults.workspace_id) {
6346
+ throw new Error("workspace_id is required for Slack discussions");
6347
+ }
6348
+ const query = new URLSearchParams();
6349
+ if (params?.limit) query.set("limit", String(params.limit));
6350
+ const suffix = query.toString() ? `?${query.toString()}` : "";
6351
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/slack/discussions${suffix}`, { method: "GET" });
6352
+ }
6353
+ /**
6354
+ * Get top Slack contributors
6355
+ */
6356
+ async slackContributors(params) {
6357
+ const withDefaults = this.withDefaults(params || {});
6358
+ if (!withDefaults.workspace_id) {
6359
+ throw new Error("workspace_id is required for Slack contributors");
6360
+ }
6361
+ const query = new URLSearchParams();
6362
+ if (params?.limit) query.set("limit", String(params.limit));
6363
+ const suffix = query.toString() ? `?${query.toString()}` : "";
6364
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/slack/contributors${suffix}`, { method: "GET" });
6365
+ }
6366
+ /**
6367
+ * Trigger a sync of Slack user profiles
6368
+ */
6369
+ async slackSyncUsers(params) {
6370
+ const withDefaults = this.withDefaults(params || {});
6371
+ if (!withDefaults.workspace_id) {
6372
+ throw new Error("workspace_id is required for syncing Slack users");
6373
+ }
6374
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/slack/sync-users`, { method: "POST" });
6375
+ }
6376
+ /**
6377
+ * Search Slack messages
6378
+ */
6379
+ async slackSearch(params) {
6380
+ const withDefaults = this.withDefaults(params || {});
6381
+ if (!withDefaults.workspace_id) {
6382
+ throw new Error("workspace_id is required for Slack search");
6383
+ }
6384
+ const query = new URLSearchParams();
6385
+ query.set("q", params.q);
6386
+ if (params?.limit) query.set("limit", String(params.limit));
6387
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/slack/search?${query.toString()}`, { method: "GET" });
6388
+ }
6389
+ // ============================================
6390
+ // GitHub Integration Methods
6391
+ // ============================================
6392
+ /**
6393
+ * Get GitHub integration statistics and overview
6394
+ */
6395
+ async githubStats(params) {
6396
+ const withDefaults = this.withDefaults(params || {});
6397
+ if (!withDefaults.workspace_id) {
6398
+ throw new Error("workspace_id is required for GitHub stats");
6399
+ }
6400
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/github/stats`, { method: "GET" });
6401
+ }
6402
+ /**
6403
+ * Get GitHub repository stats
6404
+ */
6405
+ async githubRepos(params) {
6406
+ const withDefaults = this.withDefaults(params || {});
6407
+ if (!withDefaults.workspace_id) {
6408
+ throw new Error("workspace_id is required for GitHub repos");
6409
+ }
6410
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/github/repos`, { method: "GET" });
6411
+ }
6412
+ /**
6413
+ * Get recent GitHub activity feed
6414
+ */
6415
+ async githubActivity(params) {
6416
+ const withDefaults = this.withDefaults(params || {});
6417
+ if (!withDefaults.workspace_id) {
6418
+ throw new Error("workspace_id is required for GitHub activity");
6419
+ }
6420
+ const query = new URLSearchParams();
6421
+ if (params?.limit) query.set("limit", String(params.limit));
6422
+ if (params?.offset) query.set("offset", String(params.offset));
6423
+ if (params?.repo) query.set("repo", params.repo);
6424
+ if (params?.type) query.set("type", params.type);
6425
+ const suffix = query.toString() ? `?${query.toString()}` : "";
6426
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/github/activity${suffix}`, { method: "GET" });
6427
+ }
6428
+ /**
6429
+ * Get GitHub issues and PRs
6430
+ */
6431
+ async githubIssues(params) {
6432
+ const withDefaults = this.withDefaults(params || {});
6433
+ if (!withDefaults.workspace_id) {
6434
+ throw new Error("workspace_id is required for GitHub issues");
6435
+ }
6436
+ const query = new URLSearchParams();
6437
+ if (params?.limit) query.set("limit", String(params.limit));
6438
+ if (params?.offset) query.set("offset", String(params.offset));
6439
+ if (params?.state) query.set("state", params.state);
6440
+ if (params?.repo) query.set("repo", params.repo);
6441
+ const suffix = query.toString() ? `?${query.toString()}` : "";
6442
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/github/issues${suffix}`, { method: "GET" });
6443
+ }
6444
+ /**
6445
+ * Get top GitHub contributors
6446
+ */
6447
+ async githubContributors(params) {
6448
+ const withDefaults = this.withDefaults(params || {});
6449
+ if (!withDefaults.workspace_id) {
6450
+ throw new Error("workspace_id is required for GitHub contributors");
6451
+ }
6452
+ const query = new URLSearchParams();
6453
+ if (params?.limit) query.set("limit", String(params.limit));
6454
+ const suffix = query.toString() ? `?${query.toString()}` : "";
6455
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/github/contributors${suffix}`, { method: "GET" });
6456
+ }
6457
+ /**
6458
+ * Search GitHub content
6459
+ */
6460
+ async githubSearch(params) {
6461
+ const withDefaults = this.withDefaults(params || {});
6462
+ if (!withDefaults.workspace_id) {
6463
+ throw new Error("workspace_id is required for GitHub search");
6464
+ }
6465
+ const query = new URLSearchParams();
6466
+ query.set("q", params.q);
6467
+ if (params?.limit) query.set("limit", String(params.limit));
6468
+ return request(this.config, `/workspaces/${withDefaults.workspace_id}/github/search?${query.toString()}`, { method: "GET" });
6469
+ }
6285
6470
  };
6286
6471
 
6287
6472
  // src/rules-templates.ts
@@ -6529,7 +6714,7 @@ function toStructured(data) {
6529
6714
  return void 0;
6530
6715
  }
6531
6716
  function registerTools(server, client, sessionManager) {
6532
- const upgradeUrl2 = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
6717
+ const upgradeUrl = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
6533
6718
  const defaultProTools = /* @__PURE__ */ new Set([
6534
6719
  // AI endpoints (typically paid/credit-metered)
6535
6720
  "ai_context",
@@ -6537,7 +6722,22 @@ function registerTools(server, client, sessionManager) {
6537
6722
  "ai_context_budget",
6538
6723
  "ai_embeddings",
6539
6724
  "ai_plan",
6540
- "ai_tasks"
6725
+ "ai_tasks",
6726
+ // Slack integration tools
6727
+ "slack_stats",
6728
+ "slack_channels",
6729
+ "slack_contributors",
6730
+ "slack_activity",
6731
+ "slack_discussions",
6732
+ "slack_search",
6733
+ "slack_sync_users",
6734
+ // GitHub integration tools
6735
+ "github_stats",
6736
+ "github_repos",
6737
+ "github_contributors",
6738
+ "github_activity",
6739
+ "github_issues",
6740
+ "github_search"
6541
6741
  ]);
6542
6742
  const proTools = (() => {
6543
6743
  const raw = process.env.CONTEXTSTREAM_PRO_TOOLS;
@@ -6558,7 +6758,7 @@ function registerTools(server, client, sessionManager) {
6558
6758
  return errorResult(
6559
6759
  [
6560
6760
  `Access denied: \`${toolName}\` requires ContextStream PRO.`,
6561
- `Upgrade: ${upgradeUrl2}`
6761
+ `Upgrade: ${upgradeUrl}`
6562
6762
  ].join("\n")
6563
6763
  );
6564
6764
  }
@@ -6596,7 +6796,7 @@ function registerTools(server, client, sessionManager) {
6596
6796
  title: `${config.title} (${accessLabel})`,
6597
6797
  description: `${config.description}
6598
6798
 
6599
- Access: ${accessLabel}${accessLabel === "PRO" ? ` (upgrade: ${upgradeUrl2})` : ""}`
6799
+ Access: ${accessLabel}${accessLabel === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`
6600
6800
  };
6601
6801
  const safeHandler = async (input) => {
6602
6802
  try {
@@ -6609,7 +6809,7 @@ Access: ${accessLabel}${accessLabel === "PRO" ? ` (upgrade: ${upgradeUrl2})` : "
6609
6809
  const errorCode = error?.code || error?.status || "UNKNOWN_ERROR";
6610
6810
  const isPlanLimit = String(errorCode).toUpperCase() === "FORBIDDEN" && String(errorMessage).toLowerCase().includes("plan limit reached");
6611
6811
  const upgradeHint = isPlanLimit ? `
6612
- Upgrade: ${upgradeUrl2}` : "";
6812
+ Upgrade: ${upgradeUrl}` : "";
6613
6813
  const serializedError = new Error(
6614
6814
  `[${errorCode}] ${errorMessage}${upgradeHint}${errorDetails ? `: ${JSON.stringify(errorDetails)}` : ""}`
6615
6815
  );
@@ -8510,764 +8710,345 @@ This saves ~80% tokens compared to including full chat history.`,
8510
8710
  };
8511
8711
  }
8512
8712
  );
8513
- }
8514
-
8515
- // src/resources.ts
8516
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
8517
- function wrapText(uri, text) {
8518
- return { contents: [{ uri, text }] };
8519
- }
8520
- function registerResources(server, client, apiUrl) {
8521
- server.registerResource(
8522
- "contextstream-openapi",
8523
- new ResourceTemplate("contextstream:openapi", { list: void 0 }),
8713
+ registerTool(
8714
+ "slack_stats",
8524
8715
  {
8525
- title: "ContextStream OpenAPI spec",
8526
- description: "Machine-readable OpenAPI from the configured API endpoint",
8527
- mimeType: "application/json"
8716
+ title: "Slack overview stats",
8717
+ description: `Get Slack integration statistics and overview for a workspace.
8718
+ Returns: total messages, threads, active users, channel stats, activity trends, and sync status.
8719
+ Use this to understand Slack activity and engagement patterns.`,
8720
+ inputSchema: external_exports.object({
8721
+ workspace_id: external_exports.string().uuid().optional(),
8722
+ days: external_exports.number().optional().describe("Number of days to include in stats (default: 30)")
8723
+ })
8528
8724
  },
8529
- async () => {
8530
- const uri = `${apiUrl.replace(/\/$/, "")}/api-docs/openapi.json`;
8531
- const res = await fetch(uri);
8532
- const text = await res.text();
8533
- return wrapText("contextstream:openapi", text);
8725
+ async (input) => {
8726
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8727
+ if (!workspaceId) {
8728
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8729
+ }
8730
+ const result = await client.slackStats({ workspace_id: workspaceId, days: input.days });
8731
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8534
8732
  }
8535
8733
  );
8536
- server.registerResource(
8537
- "contextstream-workspaces",
8538
- new ResourceTemplate("contextstream:workspaces", { list: void 0 }),
8539
- { title: "Workspaces", description: "List of accessible workspaces" },
8540
- async () => {
8541
- const data = await client.listWorkspaces();
8542
- return wrapText("contextstream:workspaces", JSON.stringify(data, null, 2));
8734
+ registerTool(
8735
+ "slack_channels",
8736
+ {
8737
+ title: "List Slack channels",
8738
+ description: `Get synced Slack channels with statistics for a workspace.
8739
+ Returns: channel names, message counts, thread counts, and last activity timestamps.`,
8740
+ inputSchema: external_exports.object({
8741
+ workspace_id: external_exports.string().uuid().optional()
8742
+ })
8743
+ },
8744
+ async (input) => {
8745
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8746
+ if (!workspaceId) {
8747
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8748
+ }
8749
+ const result = await client.slackChannels({ workspace_id: workspaceId });
8750
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8543
8751
  }
8544
8752
  );
8545
- server.registerResource(
8546
- "contextstream-projects",
8547
- new ResourceTemplate("contextstream:projects/{workspaceId}", { list: void 0 }),
8548
- { title: "Projects for workspace", description: "Projects in the specified workspace" },
8549
- async (uri, { workspaceId }) => {
8550
- const wsId = Array.isArray(workspaceId) ? workspaceId[0] : workspaceId;
8551
- const data = await client.listProjects({ workspace_id: wsId });
8552
- return wrapText(uri.href, JSON.stringify(data, null, 2));
8753
+ registerTool(
8754
+ "slack_contributors",
8755
+ {
8756
+ title: "Slack top contributors",
8757
+ description: `Get top Slack contributors for a workspace.
8758
+ Returns: user profiles with message counts, sorted by activity level.`,
8759
+ inputSchema: external_exports.object({
8760
+ workspace_id: external_exports.string().uuid().optional(),
8761
+ limit: external_exports.number().optional().describe("Maximum contributors to return (default: 20)")
8762
+ })
8763
+ },
8764
+ async (input) => {
8765
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8766
+ if (!workspaceId) {
8767
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8768
+ }
8769
+ const result = await client.slackContributors({ workspace_id: workspaceId, limit: input.limit });
8770
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8553
8771
  }
8554
8772
  );
8555
- }
8556
-
8557
- // src/prompts.ts
8558
- var ID_NOTES = [
8559
- "Notes:",
8560
- "- If ContextStream is not initialized in this conversation, call `session_init` first (omit ids).",
8561
- "- Do not ask me for `workspace_id`/`project_id` \u2014 use session defaults or IDs returned by `session_init`.",
8562
- "- Prefer omitting IDs in tool calls when the tool supports defaults."
8563
- ];
8564
- var upgradeUrl = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
8565
- var proPrompts = /* @__PURE__ */ new Set([
8566
- "build-context",
8567
- "generate-plan",
8568
- "generate-tasks",
8569
- "token-budget-context"
8570
- ]);
8571
- function promptAccessLabel(promptName) {
8572
- return proPrompts.has(promptName) ? "PRO" : "Free";
8573
- }
8574
- function registerPrompts(server) {
8575
- server.registerPrompt(
8576
- "explore-codebase",
8773
+ registerTool(
8774
+ "slack_activity",
8577
8775
  {
8578
- title: `Explore Codebase (${promptAccessLabel("explore-codebase")})`,
8579
- description: "Get an overview of a project codebase structure and key components"
8776
+ title: "Slack activity feed",
8777
+ description: `Get recent Slack activity feed for a workspace.
8778
+ Returns: messages with user info, reactions, replies, and timestamps.
8779
+ Can filter by channel.`,
8780
+ inputSchema: external_exports.object({
8781
+ workspace_id: external_exports.string().uuid().optional(),
8782
+ limit: external_exports.number().optional().describe("Maximum messages to return (default: 50)"),
8783
+ offset: external_exports.number().optional().describe("Pagination offset"),
8784
+ channel_id: external_exports.string().optional().describe("Filter by specific channel ID")
8785
+ })
8580
8786
  },
8581
- async () => ({
8582
- messages: [
8583
- {
8584
- role: "user",
8585
- content: {
8586
- type: "text",
8587
- text: [
8588
- "I want to understand the current codebase.",
8589
- "",
8590
- ...ID_NOTES,
8591
- "",
8592
- "Please help me by:",
8593
- "1. Use `projects_overview` to get a project summary (use session defaults; only pass `project_id` if required).",
8594
- "2. Use `projects_files` to identify key entry points.",
8595
- "3. If a focus area is clear from our conversation, prioritize it; otherwise ask me what to focus on.",
8596
- "4. Use `search_semantic` (and optionally `search_hybrid`) to find the most relevant files.",
8597
- "5. Summarize the architecture, major modules, and where to start editing."
8598
- ].join("\n")
8599
- }
8600
- }
8601
- ]
8602
- })
8787
+ async (input) => {
8788
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8789
+ if (!workspaceId) {
8790
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8791
+ }
8792
+ const result = await client.slackActivity({
8793
+ workspace_id: workspaceId,
8794
+ limit: input.limit,
8795
+ offset: input.offset,
8796
+ channel_id: input.channel_id
8797
+ });
8798
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8799
+ }
8603
8800
  );
8604
- server.registerPrompt(
8605
- "capture-decision",
8801
+ registerTool(
8802
+ "slack_discussions",
8606
8803
  {
8607
- title: `Capture Decision (${promptAccessLabel("capture-decision")})`,
8608
- description: "Document an architectural or technical decision in workspace memory"
8804
+ title: "Slack key discussions",
8805
+ description: `Get high-engagement Slack discussions/threads for a workspace.
8806
+ Returns: threads with high reply/reaction counts, sorted by engagement.
8807
+ Useful for finding important conversations and decisions.`,
8808
+ inputSchema: external_exports.object({
8809
+ workspace_id: external_exports.string().uuid().optional(),
8810
+ limit: external_exports.number().optional().describe("Maximum discussions to return (default: 20)")
8811
+ })
8609
8812
  },
8610
- async () => ({
8611
- messages: [
8612
- {
8613
- role: "user",
8614
- content: {
8615
- type: "text",
8616
- text: [
8617
- "Please capture an architectural/technical decision in ContextStream memory.",
8618
- "",
8619
- ...ID_NOTES,
8620
- "",
8621
- "Instructions:",
8622
- "- If the decision is already described in this conversation, extract: title, context, decision, consequences/tradeoffs.",
8623
- "- If anything is missing, ask me 1\u20133 quick questions to fill the gaps.",
8624
- "- Then call `session_capture` with:",
8625
- ' - event_type: "decision"',
8626
- " - title: (short ADR title)",
8627
- " - content: a well-formatted ADR (Context, Decision, Consequences)",
8628
- ' - tags: include relevant tags (e.g., "adr", "architecture")',
8629
- ' - importance: "high"',
8630
- "",
8631
- "After capturing, confirm what was saved."
8632
- ].join("\n")
8633
- }
8634
- }
8635
- ]
8636
- })
8813
+ async (input) => {
8814
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8815
+ if (!workspaceId) {
8816
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8817
+ }
8818
+ const result = await client.slackDiscussions({ workspace_id: workspaceId, limit: input.limit });
8819
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8820
+ }
8637
8821
  );
8638
- server.registerPrompt(
8639
- "review-context",
8822
+ registerTool(
8823
+ "slack_search",
8640
8824
  {
8641
- title: `Code Review Context (${promptAccessLabel("review-context")})`,
8642
- description: "Build context for reviewing code changes"
8825
+ title: "Search Slack messages",
8826
+ description: `Search Slack messages for a workspace.
8827
+ Returns: matching messages with channel, user, and engagement info.
8828
+ Use this to find specific conversations or topics.`,
8829
+ inputSchema: external_exports.object({
8830
+ workspace_id: external_exports.string().uuid().optional(),
8831
+ q: external_exports.string().describe("Search query"),
8832
+ limit: external_exports.number().optional().describe("Maximum results (default: 50)")
8833
+ })
8643
8834
  },
8644
- async () => ({
8645
- messages: [
8646
- {
8647
- role: "user",
8648
- content: {
8649
- type: "text",
8650
- text: [
8651
- "I need context to review a set of code changes.",
8652
- "",
8653
- ...ID_NOTES,
8654
- "",
8655
- "First:",
8656
- "- If file paths and a short change description are not already in this conversation, ask me for them.",
8657
- "",
8658
- "Then build review context by:",
8659
- "1. Using `graph_dependencies` to find what depends on the changed areas.",
8660
- "2. Using `graph_impact` to assess potential blast radius.",
8661
- "3. Using `memory_search` to find related decisions/notes.",
8662
- "4. Using `search_semantic` to find related code patterns.",
8663
- "",
8664
- "Provide:",
8665
- "- What the files/components do",
8666
- "- What might be affected",
8667
- "- Relevant prior decisions/lessons",
8668
- "- Review checklist + risks to focus on"
8669
- ].join("\n")
8670
- }
8671
- }
8672
- ]
8673
- })
8835
+ async (input) => {
8836
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8837
+ if (!workspaceId) {
8838
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8839
+ }
8840
+ const result = await client.slackSearch({ workspace_id: workspaceId, q: input.q, limit: input.limit });
8841
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8842
+ }
8674
8843
  );
8675
- server.registerPrompt(
8676
- "investigate-bug",
8844
+ registerTool(
8845
+ "slack_sync_users",
8677
8846
  {
8678
- title: `Investigate Bug (${promptAccessLabel("investigate-bug")})`,
8679
- description: "Build context for debugging an issue"
8847
+ title: "Sync Slack users",
8848
+ description: `Trigger a sync of Slack user profiles for a workspace.
8849
+ This fetches the latest user info from Slack and updates local profiles.
8850
+ Also auto-maps Slack users to ContextStream users by email.`,
8851
+ inputSchema: external_exports.object({
8852
+ workspace_id: external_exports.string().uuid().optional()
8853
+ })
8680
8854
  },
8681
- async () => ({
8682
- messages: [
8683
- {
8684
- role: "user",
8685
- content: {
8686
- type: "text",
8687
- text: [
8688
- "I want help investigating a bug.",
8689
- "",
8690
- ...ID_NOTES,
8691
- "",
8692
- "First:",
8693
- "- If the error/symptom is not already stated, ask me for the exact error message and what I expected vs what happened.",
8694
- "- If an affected area/component is not known, ask me where I noticed it.",
8695
- "",
8696
- "Then:",
8697
- "1. Use `search_semantic` to find code related to the error/symptom.",
8698
- "2. Use `search_pattern` to locate where similar errors are thrown or logged.",
8699
- "3. If you identify key functions, use `graph_call_path` to trace call flows.",
8700
- "4. Use `memory_search` to check if we have prior notes/bugs about this area.",
8701
- "",
8702
- "Return:",
8703
- "- Likely origin locations",
8704
- "- Call flow (if found)",
8705
- "- Related past context",
8706
- "- Suggested debugging steps"
8707
- ].join("\n")
8708
- }
8709
- }
8710
- ]
8711
- })
8712
- );
8713
- server.registerPrompt(
8714
- "explore-knowledge",
8715
- {
8716
- title: `Explore Knowledge Graph (${promptAccessLabel("explore-knowledge")})`,
8717
- description: "Navigate and understand the knowledge graph for a workspace"
8718
- },
8719
- async () => ({
8720
- messages: [
8721
- {
8722
- role: "user",
8723
- content: {
8724
- type: "text",
8725
- text: [
8726
- "Help me explore the knowledge captured in this workspace.",
8727
- "",
8728
- ...ID_NOTES,
8729
- "",
8730
- "Approach:",
8731
- "1. Use `memory_summary` for a high-level overview.",
8732
- "2. Use `memory_decisions` to see decision history (titles + a few key details).",
8733
- "3. Use `memory_list_nodes` to see available knowledge nodes.",
8734
- "4. If a starting topic is clear from the conversation, use `memory_search` for it.",
8735
- "5. Use `graph_related` on the most relevant nodes to expand connections.",
8736
- "",
8737
- "Provide:",
8738
- "- Key themes and topics",
8739
- "- Important decisions + rationale",
8740
- "- Suggested \u201Cnext nodes\u201D to explore"
8741
- ].join("\n")
8742
- }
8743
- }
8744
- ]
8745
- })
8746
- );
8747
- server.registerPrompt(
8748
- "onboard-to-project",
8749
- {
8750
- title: `Project Onboarding (${promptAccessLabel("onboard-to-project")})`,
8751
- description: "Generate onboarding context for a new team member"
8752
- },
8753
- async () => ({
8754
- messages: [
8755
- {
8756
- role: "user",
8757
- content: {
8758
- type: "text",
8759
- text: [
8760
- "Create an onboarding guide for a new team member joining this project.",
8761
- "",
8762
- ...ID_NOTES,
8763
- "",
8764
- "First:",
8765
- "- If the role is not specified, ask me what role they are onboarding into (backend, frontend, fullstack, etc.).",
8766
- "",
8767
- "Gather context:",
8768
- "1. Use `projects_overview` and `projects_statistics` for project summary.",
8769
- "2. Use `projects_files` to identify key entry points.",
8770
- "3. Use `memory_timeline` and `memory_decisions` to understand recent changes and architectural choices.",
8771
- "4. Use `search_semantic` to find READMEs/docs/setup instructions.",
8772
- "",
8773
- "Output:",
8774
- "- Project overview and purpose",
8775
- "- Tech stack + architecture map",
8776
- "- Key files/entry points relevant to the role",
8777
- "- Important decisions + rationale",
8778
- "- Recent changes/current focus",
8779
- "- Step-by-step getting started"
8780
- ].join("\n")
8781
- }
8782
- }
8783
- ]
8784
- })
8785
- );
8786
- server.registerPrompt(
8787
- "analyze-refactoring",
8788
- {
8789
- title: `Refactoring Analysis (${promptAccessLabel("analyze-refactoring")})`,
8790
- description: "Analyze a codebase for refactoring opportunities"
8791
- },
8792
- async () => ({
8793
- messages: [
8794
- {
8795
- role: "user",
8796
- content: {
8797
- type: "text",
8798
- text: [
8799
- "Analyze the codebase for refactoring opportunities.",
8800
- "",
8801
- ...ID_NOTES,
8802
- "",
8803
- "If a target area is obvious from our conversation, focus there; otherwise ask me what area to analyze.",
8804
- "",
8805
- "Please investigate:",
8806
- "1. `graph_circular_dependencies` (circular deps to break)",
8807
- "2. `graph_unused_code` (dead code to remove)",
8808
- "3. `search_pattern` (duplication patterns)",
8809
- "4. `projects_statistics` (high complexity hotspots)",
8810
- "",
8811
- "Provide a prioritized list with quick wins vs deeper refactors."
8812
- ].join("\n")
8813
- }
8814
- }
8815
- ]
8816
- })
8817
- );
8818
- server.registerPrompt(
8819
- "build-context",
8820
- {
8821
- title: `Build LLM Context (${promptAccessLabel("build-context")})`,
8822
- description: "Build comprehensive context for an LLM task"
8823
- },
8824
- async () => ({
8825
- messages: [
8826
- {
8827
- role: "user",
8828
- content: {
8829
- type: "text",
8830
- text: [
8831
- "Build comprehensive context for the task we are working on.",
8832
- "",
8833
- `Access: ${promptAccessLabel("build-context")}${promptAccessLabel("build-context") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
8834
- "",
8835
- ...ID_NOTES,
8836
- "",
8837
- "First:",
8838
- "- If the \u201Cquery\u201D is clear from the latest user request, use that.",
8839
- "- Otherwise ask me: \u201CWhat do you need context for?\u201D",
8840
- "",
8841
- "Then:",
8842
- "- Call `ai_enhanced_context` with include_code=true, include_docs=true, include_memory=true (omit IDs unless required).",
8843
- "- Synthesize the returned context into a short briefing with links/file paths and key decisions/risks."
8844
- ].join("\n")
8845
- }
8846
- }
8847
- ]
8848
- })
8849
- );
8850
- server.registerPrompt(
8851
- "smart-search",
8852
- {
8853
- title: `Smart Search (${promptAccessLabel("smart-search")})`,
8854
- description: "Search across memory, decisions, and code for a query"
8855
- },
8856
- async () => ({
8857
- messages: [
8858
- {
8859
- role: "user",
8860
- content: {
8861
- type: "text",
8862
- text: [
8863
- "Find the most relevant context for what I am asking about.",
8864
- "",
8865
- ...ID_NOTES,
8866
- "",
8867
- "First:",
8868
- "- If a query is clear from the conversation, use it.",
8869
- "- Otherwise ask me what I want to find.",
8870
- "",
8871
- "Then:",
8872
- "1. Use `session_smart_search` for the query.",
8873
- "2. If results are thin, follow up with `search_hybrid` and `memory_search`.",
8874
- "3. Return the top results with file paths/links and a short synthesis."
8875
- ].join("\n")
8876
- }
8877
- }
8878
- ]
8879
- })
8880
- );
8881
- server.registerPrompt(
8882
- "recall-context",
8883
- {
8884
- title: `Recall Context (${promptAccessLabel("recall-context")})`,
8885
- description: "Retrieve relevant past decisions and memory for a query"
8886
- },
8887
- async () => ({
8888
- messages: [
8889
- {
8890
- role: "user",
8891
- content: {
8892
- type: "text",
8893
- text: [
8894
- "Recall relevant past context (decisions, notes, lessons) for what I am asking about.",
8895
- "",
8896
- ...ID_NOTES,
8897
- "",
8898
- "First:",
8899
- "- If a recall query is clear from the conversation, use it.",
8900
- "- Otherwise ask me what topic I want to recall.",
8901
- "",
8902
- "Then:",
8903
- "- Use `session_recall` with the query (omit IDs unless required).",
8904
- "- Summarize the key points and any relevant decisions/lessons."
8905
- ].join("\n")
8906
- }
8907
- }
8908
- ]
8909
- })
8910
- );
8911
- server.registerPrompt(
8912
- "session-summary",
8913
- {
8914
- title: `Session Summary (${promptAccessLabel("session-summary")})`,
8915
- description: "Get a compact summary of workspace/project context"
8916
- },
8917
- async () => ({
8918
- messages: [
8919
- {
8920
- role: "user",
8921
- content: {
8922
- type: "text",
8923
- text: [
8924
- "Generate a compact, token-efficient summary of the current workspace/project context.",
8925
- "",
8926
- ...ID_NOTES,
8927
- "",
8928
- "Use `session_summary` (default max_tokens=500 unless I specify otherwise).",
8929
- "Then list:",
8930
- "- Top decisions (titles only)",
8931
- "- Any high-priority lessons to watch for"
8932
- ].join("\n")
8933
- }
8934
- }
8935
- ]
8936
- })
8937
- );
8938
- server.registerPrompt(
8939
- "capture-lesson",
8940
- {
8941
- title: `Capture Lesson (${promptAccessLabel("capture-lesson")})`,
8942
- description: "Record a lesson learned from an error or correction"
8943
- },
8944
- async () => ({
8945
- messages: [
8946
- {
8947
- role: "user",
8948
- content: {
8949
- type: "text",
8950
- text: [
8951
- "Capture a lesson learned so it is surfaced in future sessions.",
8952
- "",
8953
- ...ID_NOTES,
8954
- "",
8955
- "If the lesson details are not fully present in the conversation, ask me for:",
8956
- "- title (what to remember)",
8957
- "- severity (low|medium|high|critical, default medium)",
8958
- "- category (workflow|code_quality|verification|communication|project_specific)",
8959
- "- trigger (what caused it)",
8960
- "- impact (what went wrong)",
8961
- "- prevention (how to prevent it)",
8962
- "- keywords (optional)",
8963
- "",
8964
- "Then call `session_capture_lesson` with those fields and confirm it was saved."
8965
- ].join("\n")
8966
- }
8967
- }
8968
- ]
8969
- })
8970
- );
8971
- server.registerPrompt(
8972
- "capture-preference",
8973
- {
8974
- title: `Capture Preference (${promptAccessLabel("capture-preference")})`,
8975
- description: "Save a user preference to memory"
8976
- },
8977
- async () => ({
8978
- messages: [
8979
- {
8980
- role: "user",
8981
- content: {
8982
- type: "text",
8983
- text: [
8984
- "Save a user preference to ContextStream memory.",
8985
- "",
8986
- ...ID_NOTES,
8987
- "",
8988
- "If the preference is not explicit in the conversation, ask me what to remember.",
8989
- "",
8990
- "Then call `session_capture` with:",
8991
- '- event_type: "preference"',
8992
- "- title: (short title)",
8993
- "- content: (preference text)",
8994
- '- importance: "medium"'
8995
- ].join("\n")
8996
- }
8997
- }
8998
- ]
8999
- })
8855
+ async (input) => {
8856
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8857
+ if (!workspaceId) {
8858
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8859
+ }
8860
+ const result = await client.slackSyncUsers({ workspace_id: workspaceId });
8861
+ return {
8862
+ content: [{
8863
+ type: "text",
8864
+ text: `\u2705 Synced ${result.synced_users} Slack users, auto-mapped ${result.auto_mapped} by email.`
8865
+ }],
8866
+ structuredContent: toStructured(result)
8867
+ };
8868
+ }
9000
8869
  );
9001
- server.registerPrompt(
9002
- "capture-task",
8870
+ registerTool(
8871
+ "github_stats",
9003
8872
  {
9004
- title: `Capture Task (${promptAccessLabel("capture-task")})`,
9005
- description: "Capture an action item into memory"
8873
+ title: "GitHub overview stats",
8874
+ description: `Get GitHub integration statistics and overview for a workspace.
8875
+ Returns: total issues, PRs, releases, comments, repository stats, activity trends, and sync status.
8876
+ Use this to understand GitHub activity and engagement patterns across synced repositories.`,
8877
+ inputSchema: external_exports.object({
8878
+ workspace_id: external_exports.string().uuid().optional()
8879
+ })
9006
8880
  },
9007
- async () => ({
9008
- messages: [
9009
- {
9010
- role: "user",
9011
- content: {
9012
- type: "text",
9013
- text: [
9014
- "Capture an action item into ContextStream memory.",
9015
- "",
9016
- ...ID_NOTES,
9017
- "",
9018
- "If the task is not explicit in the conversation, ask me what to capture.",
9019
- "",
9020
- "Then call `session_capture` with:",
9021
- '- event_type: "task"',
9022
- "- title: (short title)",
9023
- "- content: (task details)",
9024
- '- importance: "medium"'
9025
- ].join("\n")
9026
- }
9027
- }
9028
- ]
9029
- })
8881
+ async (input) => {
8882
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8883
+ if (!workspaceId) {
8884
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8885
+ }
8886
+ const result = await client.githubStats({ workspace_id: workspaceId });
8887
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8888
+ }
9030
8889
  );
9031
- server.registerPrompt(
9032
- "capture-bug",
8890
+ registerTool(
8891
+ "github_repos",
9033
8892
  {
9034
- title: `Capture Bug (${promptAccessLabel("capture-bug")})`,
9035
- description: "Capture a bug report into workspace memory"
8893
+ title: "List GitHub repositories",
8894
+ description: `Get synced GitHub repositories with statistics for a workspace.
8895
+ Returns: repository names with issue, PR, release, and comment counts, plus last activity timestamps.`,
8896
+ inputSchema: external_exports.object({
8897
+ workspace_id: external_exports.string().uuid().optional()
8898
+ })
9036
8899
  },
9037
- async () => ({
9038
- messages: [
9039
- {
9040
- role: "user",
9041
- content: {
9042
- type: "text",
9043
- text: [
9044
- "Capture a bug report in ContextStream memory.",
9045
- "",
9046
- ...ID_NOTES,
9047
- "",
9048
- "If details are missing, ask me for:",
9049
- "- title",
9050
- "- description",
9051
- "- steps to reproduce (optional)",
9052
- "- expected behavior (optional)",
9053
- "- actual behavior (optional)",
9054
- "",
9055
- "Then call `session_capture` with:",
9056
- '- event_type: "bug"',
9057
- "- title: (bug title)",
9058
- "- content: a well-formatted bug report (include all provided details)",
9059
- "- tags: component/area tags",
9060
- '- importance: "high"'
9061
- ].join("\n")
9062
- }
9063
- }
9064
- ]
9065
- })
8900
+ async (input) => {
8901
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8902
+ if (!workspaceId) {
8903
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8904
+ }
8905
+ const result = await client.githubRepos({ workspace_id: workspaceId });
8906
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8907
+ }
9066
8908
  );
9067
- server.registerPrompt(
9068
- "capture-feature",
8909
+ registerTool(
8910
+ "github_contributors",
9069
8911
  {
9070
- title: `Capture Feature (${promptAccessLabel("capture-feature")})`,
9071
- description: "Capture a feature request into workspace memory"
8912
+ title: "GitHub top contributors",
8913
+ description: `Get top GitHub contributors for a workspace.
8914
+ Returns: usernames with contribution counts, sorted by activity level.`,
8915
+ inputSchema: external_exports.object({
8916
+ workspace_id: external_exports.string().uuid().optional(),
8917
+ limit: external_exports.number().optional().describe("Maximum contributors to return (default: 20)")
8918
+ })
9072
8919
  },
9073
- async () => ({
9074
- messages: [
9075
- {
9076
- role: "user",
9077
- content: {
9078
- type: "text",
9079
- text: [
9080
- "Capture a feature request in ContextStream memory.",
9081
- "",
9082
- ...ID_NOTES,
9083
- "",
9084
- "If details are missing, ask me for:",
9085
- "- title",
9086
- "- description",
9087
- "- rationale (optional)",
9088
- "- acceptance criteria (optional)",
9089
- "",
9090
- "Then call `session_capture` with:",
9091
- '- event_type: "feature"',
9092
- "- title: (feature title)",
9093
- "- content: a well-formatted feature request",
9094
- "- tags: component/area tags",
9095
- '- importance: "medium"'
9096
- ].join("\n")
9097
- }
9098
- }
9099
- ]
9100
- })
8920
+ async (input) => {
8921
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8922
+ if (!workspaceId) {
8923
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8924
+ }
8925
+ const result = await client.githubContributors({ workspace_id: workspaceId, limit: input.limit });
8926
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8927
+ }
9101
8928
  );
9102
- server.registerPrompt(
9103
- "generate-plan",
8929
+ registerTool(
8930
+ "github_activity",
9104
8931
  {
9105
- title: `Generate Plan (${promptAccessLabel("generate-plan")})`,
9106
- description: "Generate a development plan from a description"
8932
+ title: "GitHub activity feed",
8933
+ description: `Get recent GitHub activity feed for a workspace.
8934
+ Returns: issues, PRs, releases, and comments with details like state, author, labels.
8935
+ Can filter by repository or type (issue, pull_request, release, comment).`,
8936
+ inputSchema: external_exports.object({
8937
+ workspace_id: external_exports.string().uuid().optional(),
8938
+ limit: external_exports.number().optional().describe("Maximum items to return (default: 50)"),
8939
+ offset: external_exports.number().optional().describe("Pagination offset"),
8940
+ repo: external_exports.string().optional().describe("Filter by repository name"),
8941
+ type: external_exports.enum(["issue", "pull_request", "release", "comment"]).optional().describe("Filter by item type")
8942
+ })
9107
8943
  },
9108
- async () => ({
9109
- messages: [
9110
- {
9111
- role: "user",
9112
- content: {
9113
- type: "text",
9114
- text: [
9115
- "Generate a development plan for what I am trying to build/fix.",
9116
- "",
9117
- `Access: ${promptAccessLabel("generate-plan")}${promptAccessLabel("generate-plan") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
9118
- "",
9119
- ...ID_NOTES,
9120
- "",
9121
- "Use the most recent user request as the plan description. If unclear, ask me for a one-paragraph description.",
9122
- "",
9123
- "Then call `ai_plan` and present the plan as an ordered list with milestones and risks."
9124
- ].join("\n")
9125
- }
9126
- }
9127
- ]
9128
- })
8944
+ async (input) => {
8945
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8946
+ if (!workspaceId) {
8947
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8948
+ }
8949
+ const result = await client.githubActivity({
8950
+ workspace_id: workspaceId,
8951
+ limit: input.limit,
8952
+ offset: input.offset,
8953
+ repo: input.repo,
8954
+ type: input.type
8955
+ });
8956
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8957
+ }
9129
8958
  );
9130
- server.registerPrompt(
9131
- "generate-tasks",
8959
+ registerTool(
8960
+ "github_issues",
9132
8961
  {
9133
- title: `Generate Tasks (${promptAccessLabel("generate-tasks")})`,
9134
- description: "Generate actionable tasks from a plan or description"
8962
+ title: "GitHub issues and PRs",
8963
+ description: `Get GitHub issues and pull requests for a workspace.
8964
+ Returns: issues/PRs with title, state, author, labels, comment count.
8965
+ Can filter by state (open/closed) or repository.`,
8966
+ inputSchema: external_exports.object({
8967
+ workspace_id: external_exports.string().uuid().optional(),
8968
+ limit: external_exports.number().optional().describe("Maximum items to return (default: 50)"),
8969
+ offset: external_exports.number().optional().describe("Pagination offset"),
8970
+ state: external_exports.enum(["open", "closed"]).optional().describe("Filter by state"),
8971
+ repo: external_exports.string().optional().describe("Filter by repository name")
8972
+ })
9135
8973
  },
9136
- async () => ({
9137
- messages: [
9138
- {
9139
- role: "user",
9140
- content: {
9141
- type: "text",
9142
- text: [
9143
- "Generate actionable tasks for the work we are discussing.",
9144
- "",
9145
- `Access: ${promptAccessLabel("generate-tasks")}${promptAccessLabel("generate-tasks") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
9146
- "",
9147
- ...ID_NOTES,
9148
- "",
9149
- "If a plan_id exists in the conversation, use it. Otherwise use the latest user request as the description.",
9150
- "If granularity is not specified, default to medium.",
9151
- "",
9152
- "Call `ai_tasks` and return a checklist of tasks with acceptance criteria for each."
9153
- ].join("\n")
9154
- }
9155
- }
9156
- ]
9157
- })
8974
+ async (input) => {
8975
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
8976
+ if (!workspaceId) {
8977
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
8978
+ }
8979
+ const result = await client.githubIssues({
8980
+ workspace_id: workspaceId,
8981
+ limit: input.limit,
8982
+ offset: input.offset,
8983
+ state: input.state,
8984
+ repo: input.repo
8985
+ });
8986
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
8987
+ }
9158
8988
  );
9159
- server.registerPrompt(
9160
- "token-budget-context",
8989
+ registerTool(
8990
+ "github_search",
9161
8991
  {
9162
- title: `Token-Budget Context (${promptAccessLabel("token-budget-context")})`,
9163
- description: "Get the most relevant context that fits within a token budget"
8992
+ title: "Search GitHub content",
8993
+ description: `Search GitHub issues, PRs, and comments for a workspace.
8994
+ Returns: matching items with repository, title, state, and content preview.
8995
+ Use this to find specific issues, PRs, or discussions.`,
8996
+ inputSchema: external_exports.object({
8997
+ workspace_id: external_exports.string().uuid().optional(),
8998
+ q: external_exports.string().describe("Search query"),
8999
+ limit: external_exports.number().optional().describe("Maximum results (default: 50)")
9000
+ })
9164
9001
  },
9165
- async () => ({
9166
- messages: [
9167
- {
9168
- role: "user",
9169
- content: {
9170
- type: "text",
9171
- text: [
9172
- "Build the most relevant context that fits within a token budget.",
9173
- "",
9174
- `Access: ${promptAccessLabel("token-budget-context")}${promptAccessLabel("token-budget-context") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
9175
- "",
9176
- ...ID_NOTES,
9177
- "",
9178
- "First:",
9179
- "- If a query is clear from the conversation, use it; otherwise ask me for a query.",
9180
- "- If max_tokens is not specified, ask me for a token budget (e.g., 500/1000/2000).",
9181
- "",
9182
- "Then call `ai_context_budget` and return the packed context plus a short note about what was included/excluded."
9183
- ].join("\n")
9184
- }
9185
- }
9186
- ]
9187
- })
9002
+ async (input) => {
9003
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
9004
+ if (!workspaceId) {
9005
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
9006
+ }
9007
+ const result = await client.githubSearch({ workspace_id: workspaceId, q: input.q, limit: input.limit });
9008
+ return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
9009
+ }
9188
9010
  );
9189
- server.registerPrompt(
9190
- "find-todos",
9011
+ }
9012
+
9013
+ // src/resources.ts
9014
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
9015
+ function wrapText(uri, text) {
9016
+ return { contents: [{ uri, text }] };
9017
+ }
9018
+ function registerResources(server, client, apiUrl) {
9019
+ server.registerResource(
9020
+ "contextstream-openapi",
9021
+ new ResourceTemplate("contextstream:openapi", { list: void 0 }),
9191
9022
  {
9192
- title: `Find TODOs (${promptAccessLabel("find-todos")})`,
9193
- description: "Scan the codebase for TODO/FIXME/HACK notes and summarize"
9023
+ title: "ContextStream OpenAPI spec",
9024
+ description: "Machine-readable OpenAPI from the configured API endpoint",
9025
+ mimeType: "application/json"
9194
9026
  },
9195
- async () => ({
9196
- messages: [
9197
- {
9198
- role: "user",
9199
- content: {
9200
- type: "text",
9201
- text: [
9202
- "Scan the codebase for TODO/FIXME/HACK notes and summarize them.",
9203
- "",
9204
- ...ID_NOTES,
9205
- "",
9206
- "Use `search_pattern` with query `TODO|FIXME|HACK` (or a pattern inferred from the conversation).",
9207
- "Group results by file path, summarize themes, and propose a small prioritized cleanup list."
9208
- ].join("\n")
9209
- }
9210
- }
9211
- ]
9212
- })
9027
+ async () => {
9028
+ const uri = `${apiUrl.replace(/\/$/, "")}/api-docs/openapi.json`;
9029
+ const res = await fetch(uri);
9030
+ const text = await res.text();
9031
+ return wrapText("contextstream:openapi", text);
9032
+ }
9213
9033
  );
9214
- server.registerPrompt(
9215
- "generate-editor-rules",
9216
- {
9217
- title: `Generate Editor Rules (${promptAccessLabel("generate-editor-rules")})`,
9218
- description: "Generate ContextStream AI rule files for your editor"
9219
- },
9220
- async () => ({
9221
- messages: [
9222
- {
9223
- role: "user",
9224
- content: {
9225
- type: "text",
9226
- text: [
9227
- "Generate ContextStream AI rule files for my editor.",
9228
- "",
9229
- ...ID_NOTES,
9230
- "",
9231
- "First:",
9232
- "- If you can infer the project folder path from the environment/IDE roots, use it.",
9233
- "- Otherwise ask me for an absolute folder path.",
9234
- "- Ask which editor(s) (windsurf,cursor,cline,kilo,roo,claude,aider) or default to all.",
9235
- "",
9236
- "Then call `generate_editor_rules` and confirm which files were created/updated."
9237
- ].join("\n")
9238
- }
9239
- }
9240
- ]
9241
- })
9034
+ server.registerResource(
9035
+ "contextstream-workspaces",
9036
+ new ResourceTemplate("contextstream:workspaces", { list: void 0 }),
9037
+ { title: "Workspaces", description: "List of accessible workspaces" },
9038
+ async () => {
9039
+ const data = await client.listWorkspaces();
9040
+ return wrapText("contextstream:workspaces", JSON.stringify(data, null, 2));
9041
+ }
9242
9042
  );
9243
- server.registerPrompt(
9244
- "index-local-repo",
9245
- {
9246
- title: `Index Local Repo (${promptAccessLabel("index-local-repo")})`,
9247
- description: "Ingest local files into ContextStream for indexing/search"
9248
- },
9249
- async () => ({
9250
- messages: [
9251
- {
9252
- role: "user",
9253
- content: {
9254
- type: "text",
9255
- text: [
9256
- "Ingest local files into ContextStream for indexing/search.",
9257
- "",
9258
- ...ID_NOTES,
9259
- "",
9260
- "First:",
9261
- "- Ask me for the local directory path to ingest if it is not already specified.",
9262
- "",
9263
- "Then:",
9264
- "- Call `projects_ingest_local` with the path (use session defaults for project, or the `project_id` returned by `session_init`).",
9265
- "- Explain how to monitor progress via `projects_index_status`."
9266
- ].join("\n")
9267
- }
9268
- }
9269
- ]
9270
- })
9043
+ server.registerResource(
9044
+ "contextstream-projects",
9045
+ new ResourceTemplate("contextstream:projects/{workspaceId}", { list: void 0 }),
9046
+ { title: "Projects for workspace", description: "Projects in the specified workspace" },
9047
+ async (uri, { workspaceId }) => {
9048
+ const wsId = Array.isArray(workspaceId) ? workspaceId[0] : workspaceId;
9049
+ const data = await client.listProjects({ workspace_id: wsId });
9050
+ return wrapText(uri.href, JSON.stringify(data, null, 2));
9051
+ }
9271
9052
  );
9272
9053
  }
9273
9054
 
@@ -9633,7 +9414,6 @@ async function main() {
9633
9414
  const sessionManager = new SessionManager(server, client);
9634
9415
  registerTools(server, client, sessionManager);
9635
9416
  registerResources(server, client, config.apiUrl);
9636
- registerPrompts(server);
9637
9417
  console.error(`ContextStream MCP server v${VERSION} starting...`);
9638
9418
  console.error(`API URL: ${config.apiUrl}`);
9639
9419
  console.error(`Auth: ${config.apiKey ? "API Key" : config.jwt ? "JWT" : "None"}`);