diff --git a/CHANGELOG.md b/CHANGELOG.md index e3b0702e0..cfddaf95f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Added a `get_diff` MCP tool to return structured diffs between two git refs. [#1142](https://github.com/sourcebot-dev/sourcebot/pull/1142) + ### Changed - Synced `vendor/zoekt` with upstream `sourcegraph/zoekt`. [#1140](https://github.com/sourcebot-dev/sourcebot/pull/1140) diff --git a/docs/docs/features/mcp-server.mdx b/docs/docs/features/mcp-server.mdx index 4a97b3bb2..50adc6b02 100644 --- a/docs/docs/features/mcp-server.mdx +++ b/docs/docs/features/mcp-server.mdx @@ -388,6 +388,17 @@ Parameters: | `ref` | no | Commit SHA, branch or tag name to search on. If not provided, defaults to the default branch. | | `limit` | no | Maximum number of files to return (default: 100). | +### `get_diff` + +Returns a structured diff between two refs in a repository using a two-dot comparison. + +Parameters: +| Name | Required | Description | +|:----------|:---------|:--------------------------------------------------------------------------------------------------------------| +| `repo` | yes | The fully-qualified repository name. | +| `base` | yes | The base git ref (branch, tag, or commit SHA) to diff from. | +| `head` | yes | The head git ref (branch, tag, or commit SHA) to diff to. | + ### `find_symbol_definitions` Finds where a symbol (function, class, variable, etc.) is defined in a repository. diff --git a/packages/web/src/features/chat/agent.ts b/packages/web/src/features/chat/agent.ts index 784ca5344..0efb706fc 100644 --- a/packages/web/src/features/chat/agent.ts +++ b/packages/web/src/features/chat/agent.ts @@ -271,7 +271,7 @@ const createPrompt = ({ The user has explicitly selected the following repositories for analysis: ${repos.map(repo => `- ${repo}`).join('\n')} - When calling tools that accept a \`repo\` parameter (e.g. \`read_file\`, \`list_commits\`, \`list_tree\`, \`grep\`), use these repository names exactly as listed above, including the full host prefix (e.g. \`github.com/org/repo\`). + When calling tools that accept a \`repo\` parameter (e.g. \`read_file\`, \`list_commits\`, \`list_tree\`, \`get_diff\`, \`grep\`), use these repository names exactly as listed above, including the full host prefix (e.g. \`github.com/org/repo\`). When using \`grep\` to search across ALL selected repositories (e.g. "which repos have X?"), omit the \`repo\` parameter entirely — the tool will automatically search across all selected repositories in a single call. Do NOT call \`grep\` once per repository when a single broad search would suffice. diff --git a/packages/web/src/features/chat/components/chatThread/detailsCard.tsx b/packages/web/src/features/chat/components/chatThread/detailsCard.tsx index 72ec22b8c..0e2365ea6 100644 --- a/packages/web/src/features/chat/components/chatThread/detailsCard.tsx +++ b/packages/web/src/features/chat/components/chatThread/detailsCard.tsx @@ -19,6 +19,7 @@ import { FindSymbolDefinitionsToolComponent } from './tools/findSymbolDefinition import { FindSymbolReferencesToolComponent } from './tools/findSymbolReferencesToolComponent'; import { GlobToolComponent } from './tools/globToolComponent'; import { GrepToolComponent } from './tools/grepToolComponent'; +import { GetDiffToolComponent } from './tools/getDiffToolComponent'; import { ListCommitsToolComponent } from './tools/listCommitsToolComponent'; import { ListReposToolComponent } from './tools/listReposToolComponent'; import { ListTreeToolComponent } from './tools/listTreeToolComponent'; @@ -289,6 +290,15 @@ export const StepPartRenderer = ({ part }: { part: SBChatMessagePart }) => { {(output) => } ) + case 'tool-get_diff': + return ( + + {(output) => } + + ) case 'tool-list_tree': return ( ) => { + const fileCount = metadata.files.length; + + return ( +
+ Compared + + {metadata.base} + + to + + {metadata.head} + + in + + {metadata.repo} + + + + {fileCount} changed {fileCount === 1 ? 'file' : 'files'} + + +
+ ); +}; diff --git a/packages/web/src/features/chat/tools.ts b/packages/web/src/features/chat/tools.ts index 37ad7e0a8..149748bff 100644 --- a/packages/web/src/features/chat/tools.ts +++ b/packages/web/src/features/chat/tools.ts @@ -3,6 +3,7 @@ import { readFileDefinition, listCommitsDefinition, listReposDefinition, + getDiffDefinition, grepDefinition, globDefinition, findSymbolReferencesDefinition, @@ -17,6 +18,7 @@ export const createTools = (context: ToolContext) => ({ [readFileDefinition.name]: toVercelAITool(readFileDefinition, context), [listCommitsDefinition.name]: toVercelAITool(listCommitsDefinition, context), [listReposDefinition.name]: toVercelAITool(listReposDefinition, context), + [getDiffDefinition.name]: toVercelAITool(getDiffDefinition, context), [grepDefinition.name]: toVercelAITool(grepDefinition, context), [globDefinition.name]: toVercelAITool(globDefinition, context), [findSymbolReferencesDefinition.name]: toVercelAITool(findSymbolReferencesDefinition, context), @@ -27,6 +29,7 @@ export const createTools = (context: ToolContext) => ({ export type ReadFileToolUIPart = ToolUIPart<{ read_file: SBChatMessageToolTypes['read_file'] }>; export type ListCommitsToolUIPart = ToolUIPart<{ list_commits: SBChatMessageToolTypes['list_commits'] }>; export type ListReposToolUIPart = ToolUIPart<{ list_repos: SBChatMessageToolTypes['list_repos'] }>; +export type GetDiffToolUIPart = ToolUIPart<{ get_diff: SBChatMessageToolTypes['get_diff'] }>; export type GrepToolUIPart = ToolUIPart<{ grep: SBChatMessageToolTypes['grep'] }>; export type GlobToolUIPart = ToolUIPart<{ glob: SBChatMessageToolTypes['glob'] }>; export type FindSymbolReferencesToolUIPart = ToolUIPart<{ find_symbol_references: SBChatMessageToolTypes['find_symbol_references'] }>; diff --git a/packages/web/src/features/mcp/server.ts b/packages/web/src/features/mcp/server.ts index ec4103f36..953953f14 100644 --- a/packages/web/src/features/mcp/server.ts +++ b/packages/web/src/features/mcp/server.ts @@ -13,6 +13,7 @@ import { getConfiguredLanguageModelsInfo } from "../chat/utils.server"; import { findSymbolDefinitionsDefinition, findSymbolReferencesDefinition, + getDiffDefinition, listCommitsDefinition, listReposDefinition, listTreeDefinition, @@ -40,6 +41,7 @@ export async function createMcpServer(): Promise { registerMcpTool(server, grepDefinition, toolContext); registerMcpTool(server, globDefinition, toolContext); + registerMcpTool(server, getDiffDefinition, toolContext); registerMcpTool(server, listCommitsDefinition, toolContext); registerMcpTool(server, listReposDefinition, toolContext); registerMcpTool(server, readFileDefinition, toolContext); diff --git a/packages/web/src/features/tools/getDiff.ts b/packages/web/src/features/tools/getDiff.ts new file mode 100644 index 000000000..803a9429d --- /dev/null +++ b/packages/web/src/features/tools/getDiff.ts @@ -0,0 +1,40 @@ +import { getDiff, GetDiffResult } from '@/features/git'; +import { getDiffRequestSchema } from '@/features/git/schemas'; +import { isServiceError } from '@/lib/utils'; +import description from './getDiff.txt'; +import { logger } from './logger'; +import { ToolDefinition } from './types'; + +export type GetDiffMetadata = GetDiffResult & { + repo: string; + base: string; + head: string; +}; + +export const getDiffDefinition: ToolDefinition<'get_diff', typeof getDiffRequestSchema.shape, GetDiffMetadata> = { + name: 'get_diff', + title: 'Get diff', + isReadOnly: true, + isIdempotent: true, + description, + inputSchema: getDiffRequestSchema, + execute: async ({ repo, base, head }, _context) => { + logger.debug('get_diff', { repo, base, head }); + + const response = await getDiff({ repo, base, head }); + + if (isServiceError(response)) { + throw new Error(response.message); + } + + return { + output: JSON.stringify(response), + metadata: { + ...response, + repo, + base, + head, + }, + }; + }, +}; diff --git a/packages/web/src/features/tools/getDiff.txt b/packages/web/src/features/tools/getDiff.txt new file mode 100644 index 000000000..84cea0153 --- /dev/null +++ b/packages/web/src/features/tools/getDiff.txt @@ -0,0 +1,4 @@ +Returns a structured git diff between two refs in a repository. + +Use this tool when you need patch details (added, removed, and context lines) between a base and head revision. +The output includes changed files and hunks with line ranges and diff body content. diff --git a/packages/web/src/features/tools/index.ts b/packages/web/src/features/tools/index.ts index 1a8f04013..04ac7014d 100644 --- a/packages/web/src/features/tools/index.ts +++ b/packages/web/src/features/tools/index.ts @@ -3,6 +3,7 @@ export * from './listCommits'; export * from './listRepos'; export * from './grep'; export * from './glob'; +export * from './getDiff'; export * from './findSymbolReferences'; export * from './findSymbolDefinitions'; export * from './listTree';