Skip to content

Commit 14ca65d

Browse files
authored
feat(web): expose get_diff on MCP server (#1142)
* feat(web): expose get_diff on MCP server Add a new get_diff MCP tool backed by the existing git diff API so agents can request structured diffs between refs. Document the tool in MCP docs with parameters aligned to the public API spec. Made-with: Cursor * chore: add changelog entry for MCP get_diff tool Document the new MCP get_diff capability under Unreleased using the repository changelog format and PR link. Made-with: Cursor * feat(chat): expose get_diff tool in Ask agent Register get_diff in the Ask agent toolset and render its output in the details panel so chat workflows can compare refs directly. Made-with: Cursor
1 parent 48ad44d commit 14ca65d

File tree

10 files changed

+105
-1
lines changed

10 files changed

+105
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Added a `get_diff` MCP tool to return structured diffs between two git refs. [#1142](https://github.com/sourcebot-dev/sourcebot/pull/1142)
12+
1013
### Changed
1114
- Synced `vendor/zoekt` with upstream `sourcegraph/zoekt`. [#1140](https://github.com/sourcebot-dev/sourcebot/pull/1140)
1215

docs/docs/features/mcp-server.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,17 @@ Parameters:
388388
| `ref` | no | Commit SHA, branch or tag name to search on. If not provided, defaults to the default branch. |
389389
| `limit` | no | Maximum number of files to return (default: 100). |
390390

391+
### `get_diff`
392+
393+
Returns a structured diff between two refs in a repository using a two-dot comparison.
394+
395+
Parameters:
396+
| Name | Required | Description |
397+
|:----------|:---------|:--------------------------------------------------------------------------------------------------------------|
398+
| `repo` | yes | The fully-qualified repository name. |
399+
| `base` | yes | The base git ref (branch, tag, or commit SHA) to diff from. |
400+
| `head` | yes | The head git ref (branch, tag, or commit SHA) to diff to. |
401+
391402
### `find_symbol_definitions`
392403

393404
Finds where a symbol (function, class, variable, etc.) is defined in a repository.

packages/web/src/features/chat/agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ const createPrompt = ({
271271
The user has explicitly selected the following repositories for analysis:
272272
${repos.map(repo => `- ${repo}`).join('\n')}
273273
274-
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\`).
274+
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\`).
275275
276276
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.
277277
</selected_repositories>

packages/web/src/features/chat/components/chatThread/detailsCard.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { FindSymbolDefinitionsToolComponent } from './tools/findSymbolDefinition
1919
import { FindSymbolReferencesToolComponent } from './tools/findSymbolReferencesToolComponent';
2020
import { GlobToolComponent } from './tools/globToolComponent';
2121
import { GrepToolComponent } from './tools/grepToolComponent';
22+
import { GetDiffToolComponent } from './tools/getDiffToolComponent';
2223
import { ListCommitsToolComponent } from './tools/listCommitsToolComponent';
2324
import { ListReposToolComponent } from './tools/listReposToolComponent';
2425
import { ListTreeToolComponent } from './tools/listTreeToolComponent';
@@ -289,6 +290,15 @@ export const StepPartRenderer = ({ part }: { part: SBChatMessagePart }) => {
289290
{(output) => <ListCommitsToolComponent {...output} />}
290291
</ToolOutputGuard>
291292
)
293+
case 'tool-get_diff':
294+
return (
295+
<ToolOutputGuard
296+
part={part}
297+
loadingText="Comparing revisions..."
298+
>
299+
{(output) => <GetDiffToolComponent {...output} />}
300+
</ToolOutputGuard>
301+
)
292302
case 'tool-list_tree':
293303
return (
294304
<ToolOutputGuard
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use client';
2+
3+
import { Separator } from '@/components/ui/separator';
4+
import { GetDiffMetadata, ToolResult } from '@/features/tools';
5+
6+
export const GetDiffToolComponent = ({ metadata }: ToolResult<GetDiffMetadata>) => {
7+
const fileCount = metadata.files.length;
8+
9+
return (
10+
<div className="flex items-center gap-2 select-none cursor-default text-sm text-muted-foreground">
11+
<span className="flex-shrink-0">Compared</span>
12+
<span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded text-foreground">
13+
{metadata.base}
14+
</span>
15+
<span className="flex-shrink-0">to</span>
16+
<span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded text-foreground">
17+
{metadata.head}
18+
</span>
19+
<span className="flex-shrink-0">in</span>
20+
<span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded text-foreground truncate">
21+
{metadata.repo}
22+
</span>
23+
<span className="flex-1" />
24+
<span className="text-xs flex-shrink-0">
25+
{fileCount} changed {fileCount === 1 ? 'file' : 'files'}
26+
</span>
27+
<Separator orientation="vertical" className="h-3 flex-shrink-0" />
28+
</div>
29+
);
30+
};

packages/web/src/features/chat/tools.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
readFileDefinition,
44
listCommitsDefinition,
55
listReposDefinition,
6+
getDiffDefinition,
67
grepDefinition,
78
globDefinition,
89
findSymbolReferencesDefinition,
@@ -17,6 +18,7 @@ export const createTools = (context: ToolContext) => ({
1718
[readFileDefinition.name]: toVercelAITool(readFileDefinition, context),
1819
[listCommitsDefinition.name]: toVercelAITool(listCommitsDefinition, context),
1920
[listReposDefinition.name]: toVercelAITool(listReposDefinition, context),
21+
[getDiffDefinition.name]: toVercelAITool(getDiffDefinition, context),
2022
[grepDefinition.name]: toVercelAITool(grepDefinition, context),
2123
[globDefinition.name]: toVercelAITool(globDefinition, context),
2224
[findSymbolReferencesDefinition.name]: toVercelAITool(findSymbolReferencesDefinition, context),
@@ -27,6 +29,7 @@ export const createTools = (context: ToolContext) => ({
2729
export type ReadFileToolUIPart = ToolUIPart<{ read_file: SBChatMessageToolTypes['read_file'] }>;
2830
export type ListCommitsToolUIPart = ToolUIPart<{ list_commits: SBChatMessageToolTypes['list_commits'] }>;
2931
export type ListReposToolUIPart = ToolUIPart<{ list_repos: SBChatMessageToolTypes['list_repos'] }>;
32+
export type GetDiffToolUIPart = ToolUIPart<{ get_diff: SBChatMessageToolTypes['get_diff'] }>;
3033
export type GrepToolUIPart = ToolUIPart<{ grep: SBChatMessageToolTypes['grep'] }>;
3134
export type GlobToolUIPart = ToolUIPart<{ glob: SBChatMessageToolTypes['glob'] }>;
3235
export type FindSymbolReferencesToolUIPart = ToolUIPart<{ find_symbol_references: SBChatMessageToolTypes['find_symbol_references'] }>;

packages/web/src/features/mcp/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getConfiguredLanguageModelsInfo } from "../chat/utils.server";
1313
import {
1414
findSymbolDefinitionsDefinition,
1515
findSymbolReferencesDefinition,
16+
getDiffDefinition,
1617
listCommitsDefinition,
1718
listReposDefinition,
1819
listTreeDefinition,
@@ -40,6 +41,7 @@ export async function createMcpServer(): Promise<McpServer> {
4041

4142
registerMcpTool(server, grepDefinition, toolContext);
4243
registerMcpTool(server, globDefinition, toolContext);
44+
registerMcpTool(server, getDiffDefinition, toolContext);
4345
registerMcpTool(server, listCommitsDefinition, toolContext);
4446
registerMcpTool(server, listReposDefinition, toolContext);
4547
registerMcpTool(server, readFileDefinition, toolContext);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { getDiff, GetDiffResult } from '@/features/git';
2+
import { getDiffRequestSchema } from '@/features/git/schemas';
3+
import { isServiceError } from '@/lib/utils';
4+
import description from './getDiff.txt';
5+
import { logger } from './logger';
6+
import { ToolDefinition } from './types';
7+
8+
export type GetDiffMetadata = GetDiffResult & {
9+
repo: string;
10+
base: string;
11+
head: string;
12+
};
13+
14+
export const getDiffDefinition: ToolDefinition<'get_diff', typeof getDiffRequestSchema.shape, GetDiffMetadata> = {
15+
name: 'get_diff',
16+
title: 'Get diff',
17+
isReadOnly: true,
18+
isIdempotent: true,
19+
description,
20+
inputSchema: getDiffRequestSchema,
21+
execute: async ({ repo, base, head }, _context) => {
22+
logger.debug('get_diff', { repo, base, head });
23+
24+
const response = await getDiff({ repo, base, head });
25+
26+
if (isServiceError(response)) {
27+
throw new Error(response.message);
28+
}
29+
30+
return {
31+
output: JSON.stringify(response),
32+
metadata: {
33+
...response,
34+
repo,
35+
base,
36+
head,
37+
},
38+
};
39+
},
40+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Returns a structured git diff between two refs in a repository.
2+
3+
Use this tool when you need patch details (added, removed, and context lines) between a base and head revision.
4+
The output includes changed files and hunks with line ranges and diff body content.

packages/web/src/features/tools/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './listCommits';
33
export * from './listRepos';
44
export * from './grep';
55
export * from './glob';
6+
export * from './getDiff';
67
export * from './findSymbolReferences';
78
export * from './findSymbolDefinitions';
89
export * from './listTree';

0 commit comments

Comments
 (0)