@chapterai/mcp 0.1.4 → 0.1.7

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 (2) hide show
  1. package/dist/index.js +64 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23567,6 +23567,15 @@ ${lines.join("\n")}`);
23567
23567
  }
23568
23568
 
23569
23569
  // src/tools/intelligence.ts
23570
+ function renderSnippet(snippet) {
23571
+ if (!snippet) return "";
23572
+ const padWidth = Math.max(4, String(snippet.endLine).length);
23573
+ const body = snippet.lines.map((line, i) => `${String(snippet.startLine + i).padStart(padWidth, " ")} | ${line}`).join("\n");
23574
+ return `
23575
+ \`\`\`
23576
+ ${body}
23577
+ \`\`\``;
23578
+ }
23570
23579
  function registerIntelligenceTools(server2) {
23571
23580
  server2.tool(
23572
23581
  "scan_codebase",
@@ -23608,48 +23617,49 @@ function registerIntelligenceTools(server2) {
23608
23617
  );
23609
23618
  server2.tool(
23610
23619
  "query_codebase",
23611
- "Search the codebase index for files, symbols, routes, endpoints, and copy matching a query. Returns ranked results with file paths and line numbers. The index must exist (run scan_codebase first).",
23620
+ "Search the codebase index for files, symbols, routes, endpoints, and copy matching a query. Returns ranked results with file paths, line numbers, and inline source snippets so you usually don't need a follow-up read_file call.",
23612
23621
  {
23613
23622
  workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23614
23623
  workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23615
23624
  query: external_exports.string().describe('Search query \u2014 e.g. "task creation", "auth middleware", "welcome page"'),
23616
- limit: external_exports.number().optional().describe("Max results per category (default 20)")
23625
+ limit: external_exports.number().optional().describe("Max results per category (default 20)"),
23626
+ contextLines: external_exports.number().int().min(0).max(20).default(6).describe("Lines of source to include around each hit. 0 disables snippets.")
23617
23627
  },
23618
- async ({ workspaceId, workspaceName, query, limit }) => {
23628
+ async ({ workspaceId, workspaceName, query, limit, contextLines }) => {
23619
23629
  try {
23620
23630
  const id = await resolveWorkspaceId(workspaceId, workspaceName);
23621
- const result = await api.intelligence.query({ workspaceId: id, query, limit });
23631
+ const result = await api.intelligence.query({ workspaceId: id, query, limit, contextLines });
23622
23632
  const sections = [];
23623
23633
  if (result.files.length > 0) {
23624
23634
  sections.push("## Files");
23625
23635
  for (const f of result.files) {
23626
23636
  const exports = f.exports.length > 0 ? ` (exports: ${f.exports.slice(0, 3).join(", ")})` : "";
23627
- sections.push(`- \`${f.path}\` [${f.category}]${exports}`);
23637
+ sections.push(`- \`${f.path}\` [${f.category}]${exports}${renderSnippet(f.snippet)}`);
23628
23638
  }
23629
23639
  }
23630
23640
  if (result.symbols.length > 0) {
23631
23641
  sections.push("\n## Symbols");
23632
23642
  for (const s of result.symbols) {
23633
23643
  const sig = s.signature ? ` \u2014 \`${s.signature}\`` : "";
23634
- sections.push(`- \`${s.name}\` (${s.kind}) at \`${s.filePath}:${s.line}\`${sig}`);
23644
+ sections.push(`- \`${s.name}\` (${s.kind}) at \`${s.filePath}:${s.line}\`${sig}${renderSnippet(s.snippet)}`);
23635
23645
  }
23636
23646
  }
23637
23647
  if (result.copy.length > 0) {
23638
23648
  sections.push("\n## Copy/Text");
23639
23649
  for (const c of result.copy) {
23640
- sections.push(`- "${c.text}" at \`${c.filePath}:${c.line}\` in ${c.context}`);
23650
+ sections.push(`- "${c.text}" at \`${c.filePath}:${c.line}\` in ${c.context}${renderSnippet(c.snippet)}`);
23641
23651
  }
23642
23652
  }
23643
23653
  if (result.routes.length > 0) {
23644
23654
  sections.push("\n## Routes");
23645
23655
  for (const r of result.routes) {
23646
- sections.push(`- ${r.method ?? "ANY"} \`${r.path}\` \u2192 ${r.handler} at \`${r.filePath}:${r.line}\``);
23656
+ sections.push(`- ${r.method ?? "ANY"} \`${r.path}\` \u2192 ${r.handler} at \`${r.filePath}:${r.line}\`${renderSnippet(r.snippet)}`);
23647
23657
  }
23648
23658
  }
23649
23659
  if (result.endpoints.length > 0) {
23650
23660
  sections.push("\n## API Endpoints");
23651
23661
  for (const e of result.endpoints) {
23652
- sections.push(`- \`${e.router ? e.router + "." : ""}${e.name}\` (${e.type}) at \`${e.filePath}:${e.line}\``);
23662
+ sections.push(`- \`${e.router ? e.router + "." : ""}${e.name}\` (${e.type}) at \`${e.filePath}:${e.line}\`${renderSnippet(e.snippet)}`);
23653
23663
  }
23654
23664
  }
23655
23665
  if (sections.length === 0) {
@@ -23663,24 +23673,30 @@ function registerIntelligenceTools(server2) {
23663
23673
  );
23664
23674
  server2.tool(
23665
23675
  "find_symbol",
23666
- "Find a specific function, class, component, type, or other symbol by name. Returns exact file path and line number.",
23676
+ "Find a specific function, class, component, type, or other symbol by name. Returns exact file path, line number, and an inline source snippet so the symbol body is right there.",
23667
23677
  {
23668
23678
  workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23669
23679
  workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23670
23680
  name: external_exports.string().describe('Symbol name to search for \u2014 e.g. "TaskCreateInline", "authorizeWorkspaceAccess"'),
23671
- kind: external_exports.string().optional().describe("Filter by kind: function, class, component, type, interface, enum, hook, struct, method")
23681
+ kind: external_exports.string().optional().describe("Filter by kind: function, class, component, type, interface, enum, hook, struct, method"),
23682
+ contextLines: external_exports.number().int().min(0).max(20).default(8).describe("Lines of source to include around each hit. 0 disables snippets.")
23672
23683
  },
23673
- async ({ workspaceId, workspaceName, name, kind }) => {
23684
+ async ({ workspaceId, workspaceName, name, kind, contextLines }) => {
23674
23685
  try {
23675
23686
  const id = await resolveWorkspaceId(workspaceId, workspaceName);
23676
- const results = await api.intelligence.findSymbol({ workspaceId: id, name, kind });
23687
+ const results = await api.intelligence.findSymbol({
23688
+ workspaceId: id,
23689
+ name,
23690
+ kind,
23691
+ contextLines
23692
+ });
23677
23693
  if (results.length === 0) {
23678
23694
  return success(`No symbol found matching "${name}"${kind ? ` (kind: ${kind})` : ""}`);
23679
23695
  }
23680
23696
  const lines = results.map((s) => {
23681
23697
  const sig = s.signature ? `
23682
23698
  ${s.signature}` : "";
23683
- return `- \`${s.name}\` (${s.kind}) at \`${s.filePath}:${s.line}\`${s.exported ? " [exported]" : ""}${sig}`;
23699
+ return `- \`${s.name}\` (${s.kind}) at \`${s.filePath}:${s.line}\`${s.exported ? " [exported]" : ""}${sig}${renderSnippet(s.snippet)}`;
23684
23700
  });
23685
23701
  return success(`Found ${results.length} match(es):
23686
23702
  ${lines.join("\n")}`);
@@ -23691,21 +23707,26 @@ ${lines.join("\n")}`);
23691
23707
  );
23692
23708
  server2.tool(
23693
23709
  "find_copy",
23694
- "Find user-facing text/copy in the codebase by content. Returns the file path, line number, and surrounding component context. Use this to quickly locate where a specific string appears in the UI.",
23710
+ "Find user-facing text/copy in the codebase by content. Returns the file path, line number, surrounding component context, and an inline JSX/template snippet.",
23695
23711
  {
23696
23712
  workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23697
23713
  workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23698
- text: external_exports.string().describe('Text to search for \u2014 e.g. "Welcome to Chapter", "Sign in", "No tasks yet"')
23714
+ text: external_exports.string().describe('Text to search for \u2014 e.g. "Welcome to Chapter", "Sign in", "No tasks yet"'),
23715
+ contextLines: external_exports.number().int().min(0).max(20).default(4).describe("Lines of source to include around each hit. 0 disables snippets.")
23699
23716
  },
23700
- async ({ workspaceId, workspaceName, text }) => {
23717
+ async ({ workspaceId, workspaceName, text, contextLines }) => {
23701
23718
  try {
23702
23719
  const id = await resolveWorkspaceId(workspaceId, workspaceName);
23703
- const results = await api.intelligence.findCopy({ workspaceId: id, text });
23720
+ const results = await api.intelligence.findCopy({
23721
+ workspaceId: id,
23722
+ text,
23723
+ contextLines
23724
+ });
23704
23725
  if (results.length === 0) {
23705
23726
  return success(`No copy found matching "${text}"`);
23706
23727
  }
23707
23728
  const lines = results.map(
23708
- (c) => `- "${c.text}" at \`${c.filePath}:${c.line}\` in \`${c.context}\` (${c.type})`
23729
+ (c) => `- "${c.text}" at \`${c.filePath}:${c.line}\` in \`${c.context}\` (${c.type})${renderSnippet(c.snippet)}`
23709
23730
  );
23710
23731
  return success(`Found ${results.length} match(es):
23711
23732
  ${lines.join("\n")}`);
@@ -23714,15 +23735,37 @@ ${lines.join("\n")}`);
23714
23735
  }
23715
23736
  }
23716
23737
  );
23738
+ server2.tool(
23739
+ "answer_about_codebase",
23740
+ 'Ask a question about the codebase and get a complete, cited answer in one call. Reads the most relevant ~8 files server-side and returns a synthesized markdown answer with inline path:line citations on every concrete claim. ASK FIRST for any "how does X work", "where is X created/used across the codebase", or "walk me through the flow from A to B" question \u2014 this replaces the typical query_codebase + 4-6 read_file slices loop with a single round trip. Only fall back to read_file when you need verbatim code beyond what the answer cites.',
23741
+ {
23742
+ workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23743
+ workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23744
+ taskDescription: external_exports.string().describe('Question or task \u2014 e.g. "how does the permission system work?", "where is sessionEnded emitted across the codebase?", "walk me through what happens when a user clicks Apply Changes"')
23745
+ },
23746
+ async ({ workspaceId, workspaceName, taskDescription }) => {
23747
+ try {
23748
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23749
+ const result = await api.intelligence.contextForTask({
23750
+ workspaceId: id,
23751
+ taskDescription
23752
+ });
23753
+ return success(result.context);
23754
+ } catch (error2) {
23755
+ return formatError2(error2);
23756
+ }
23757
+ }
23758
+ );
23717
23759
  server2.tool(
23718
23760
  "get_codebase_context",
23719
- "Ask a question about the codebase or describe a task \u2014 returns an AI-synthesized answer with specific file paths, function names, and implementation details. The answer is ready to use: start working immediately based on it. Only read files if you need to see exact code not covered in the answer.",
23761
+ "(deprecated alias for answer_about_codebase) Ask a question about the codebase and get a cited answer. This name will be removed in 0.3.0; switch your callsites to answer_about_codebase.",
23720
23762
  {
23721
23763
  workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23722
23764
  workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23723
- taskDescription: external_exports.string().describe('Question or task description \u2014 e.g. "how does the permission system work?" or "change the welcome message on the home page"')
23765
+ taskDescription: external_exports.string().describe("Question or task description")
23724
23766
  },
23725
23767
  async ({ workspaceId, workspaceName, taskDescription }) => {
23768
+ console.error("get_codebase_context is deprecated; use answer_about_codebase");
23726
23769
  try {
23727
23770
  const id = await resolveWorkspaceId(workspaceId, workspaceName);
23728
23771
  const result = await api.intelligence.contextForTask({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chapterai/mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "description": "Chapter MCP server — gives AI agents access to Chapter projects, files, changes, and history",
5
5
  "type": "module",
6
6
  "bin": {