Skip to content

Commit f62c759

Browse files
feat(mcp): ask subagent (#814)
1 parent 6b79cbf commit f62c759

File tree

27 files changed

+844
-665
lines changed

27 files changed

+844
-665
lines changed

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
- Added `/api/chat/blocking` endpoint that creates a blocking Ask thread, used by the MCP server. [#814](https://github.com/sourcebot-dev/sourcebot/pull/814)
12+
- Added `/api/models` endpoint to list configured language models. [#814](https://github.com/sourcebot-dev/sourcebot/pull/814)
13+
- Added additional telemetry for api requests. [#835](https://github.com/sourcebot-dev/sourcebot/pull/835)
14+
1015
### Fixed
1116
- Fixed issue where files with a comma would not render correctly in file tree. [#831](https://github.com/sourcebot-dev/sourcebot/pull/831)
1217

1318
### Changed
1419
- Changed `/api/source` api to support fetching source code for any revision, not just revisions that are indexed by zoekt. [#829](https://github.com/sourcebot-dev/sourcebot/pull/829)
15-
- Added additional telemetry for api requests. [#835](https://github.com/sourcebot-dev/sourcebot/pull/835)
20+
- Adjusted prompts and tools for Ask agent. [#814](https://github.com/sourcebot-dev/sourcebot/pull/814)
1621

1722
## [4.10.20] - 2026-01-28
1823

docs/docs/features/mcp-server.mdx

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,32 +147,84 @@ The [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP)
147147

148148
### `search_code`
149149

150-
Fetches code that matches the provided regex pattern in `query`.
150+
Searches for code that matches the provided search query as a substring by default, or as a regular expression if `useRegex` is true.
151151

152152
Parameters:
153-
| Name | Required | Description |
154-
|:----------------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------|
155-
| `query` | yes | Regex pattern to search for. Escape special characters and spaces with a single backslash (e.g., 'console\.log', 'console\ log'). |
156-
| `filterByRepoIds` | no | Restrict search to specific repository IDs (from 'list_repos'). Leave empty to search all. |
157-
| `filterByLanguages` | no | Restrict search to specific languages (GitHub linguist format, e.g., Python, JavaScript). |
158-
| `caseSensitive` | no | Case sensitive search (default: false). |
159-
| `includeCodeSnippets` | no | Include code snippets in results (default: false). |
160-
| `maxTokens` | no | Max tokens to return (default: env.DEFAULT_MINIMUM_TOKENS). |
153+
| Name | Required | Description |
154+
|:----------------------|:---------|:---------------------------------------------------------------------------------------------------------------------|
155+
| `query` | yes | The search pattern to match against code contents. Do not escape quotes in your query. |
156+
| `useRegex` | no | Whether to use regular expression matching. When false, substring matching is used (default: false). |
157+
| `filterByRepos` | no | Scope the search to specific repositories. |
158+
| `filterByLanguages` | no | Scope the search to specific languages. |
159+
| `filterByFilepaths` | no | Scope the search to specific filepaths. |
160+
| `caseSensitive` | no | Whether the search should be case sensitive (default: false). |
161+
| `includeCodeSnippets` | no | Whether to include code snippets in the response (default: false). |
162+
| `ref` | no | Commit SHA, branch or tag name to search on. If not provided, defaults to the default branch. |
163+
| `maxTokens` | no | The maximum number of tokens to return (default: 10000). |
161164

162165

163166
### `list_repos`
164167

165-
Lists all repositories indexed by Sourcebot.
168+
Lists repositories indexed by Sourcebot with optional filtering and pagination.
166169

167-
### `get_file_source`
170+
Parameters:
171+
| Name | Required | Description |
172+
|:------------|:---------|:--------------------------------------------------------------------------------|
173+
| `query` | no | Filter repositories by name (case-insensitive). |
174+
| `page` | no | Page number for pagination (min 1, default: 1). |
175+
| `perPage` | no | Results per page for pagination (min 1, max 100, default: 30). |
176+
| `sort` | no | Sort repositories by 'name' or 'pushed' (most recent commit). Default: 'name'. |
177+
| `direction` | no | Sort direction: 'asc' or 'desc' (default: 'asc'). |
178+
179+
180+
### `read_file`
181+
182+
Reads the source code for a given file.
183+
184+
Parameters:
185+
| Name | Required | Description |
186+
|:-------|:---------|:-------------------------------------------------------------------------------------------------------|
187+
| `repo` | yes | The repository name. |
188+
| `path` | yes | The path to the file. |
189+
| `ref` | no | Commit SHA, branch or tag name to fetch the source code for. If not provided, uses the default branch. |
190+
191+
192+
### `list_commits`
193+
194+
Get a list of commits for a given repository.
195+
196+
Parameters:
197+
| Name | Required | Description |
198+
|:----------|:---------|:-----------------------------------------------------------------------------------------------------------------------|
199+
| `repo` | yes | The name of the repository to list commits for. |
200+
| `query` | no | Search query to filter commits by message content (case-insensitive). |
201+
| `since` | no | Show commits more recent than this date. Supports ISO 8601 (e.g., '2024-01-01') or relative formats (e.g., '30 days ago'). |
202+
| `until` | no | Show commits older than this date. Supports ISO 8601 (e.g., '2024-12-31') or relative formats (e.g., 'yesterday'). |
203+
| `author` | no | Filter commits by author name or email (case-insensitive). |
204+
| `ref` | no | Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch. |
205+
| `page` | no | Page number for pagination (min 1, default: 1). |
206+
| `perPage` | no | Results per page for pagination (min 1, max 100, default: 50). |
207+
208+
209+
### `list_language_models`
210+
211+
Lists the available language models configured on the Sourcebot instance. Use this to discover which models can be specified when calling `ask_codebase`.
212+
213+
Parameters:
214+
215+
This tool takes no parameters.
216+
217+
218+
### `ask_codebase`
168219

169-
Fetches the source code for a given file.
220+
Ask a natural language question about the codebase. This tool uses an AI agent to autonomously search code, read files, and find symbol references/definitions to answer your question. Returns a detailed answer in markdown format with code references, plus a link to view the full research session in the Sourcebot web UI.
170221

171222
Parameters:
172-
| Name | Required | Description |
173-
|:-------------|:---------|:-----------------------------------------------------------------|
174-
| `fileName` | yes | The file to fetch the source code for. |
175-
| `repoId` | yes | The Sourcebot repository ID. |
223+
| Name | Required | Description |
224+
|:----------------|:---------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
225+
| `query` | yes | The query to ask about the codebase. |
226+
| `repos` | no | The repositories that are accessible to the agent during the chat. If not provided, all repositories are accessible. |
227+
| `languageModel` | no | The language model to use for answering the question. Object with `provider` and `model`. If not provided, defaults to the first model in the config. Use `list_language_models` to see available options. |
176228

177229

178230
## Environment Variables

packages/mcp/CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
- Added `ask_codebase` tool that can invoke the Ask subagent to explore a set of codebases and return a summarized answer. [#814](https://github.com/sourcebot-dev/sourcebot/pull/814)
12+
- Added `list_language_models` tool to discover available language models configured on the Sourcebot instance. [#814](https://github.com/sourcebot-dev/sourcebot/pull/814)
13+
1014
## [1.0.14] - 2026-01-27
1115

1216
### Changed

packages/mcp/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,32 @@ Get a list of commits for a given repository.
234234

235235
</details>
236236

237+
### list_language_models
238+
239+
Lists the available language models configured on the Sourcebot instance. Use this to discover which models can be specified when calling `ask_codebase`.
240+
241+
<details>
242+
<summary>Parameters</summary>
243+
244+
This tool takes no parameters.
245+
246+
</details>
247+
248+
### ask_codebase
249+
250+
Ask a natural language question about the codebase. This tool uses an AI agent to autonomously search code, read files, and find symbol references/definitions to answer your question. Returns a detailed answer in markdown format with code references, plus a link to view the full research session in the Sourcebot web UI.
251+
252+
<details>
253+
<summary>Parameters</summary>
254+
255+
| Name | Required | Description |
256+
|:----------------|:---------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
257+
| `query` | yes | The query to ask about the codebase. |
258+
| `repos` | no | The repositories that are accessible to the agent during the chat. If not provided, all repositories are accessible. |
259+
| `languageModel` | no | The language model to use for answering the question. Object with `provider` and `model`. If not provided, defaults to the first model in the config. Use `list_language_models` to see available options. |
260+
261+
</details>
262+
237263

238264
## Supported Code Hosts
239265
Sourcebot supports the following code hosts:

packages/mcp/src/client.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { env } from './env.js';
2-
import { listReposResponseSchema, searchResponseSchema, fileSourceResponseSchema, listCommitsResponseSchema } from './schemas.js';
3-
import { FileSourceRequest, ListReposQueryParams, SearchRequest, ListCommitsQueryParamsSchema } from './types.js';
2+
import { listReposResponseSchema, searchResponseSchema, fileSourceResponseSchema, listCommitsResponseSchema, askCodebaseResponseSchema, listLanguageModelsResponseSchema } from './schemas.js';
3+
import { AskCodebaseRequest, AskCodebaseResponse, FileSourceRequest, ListReposQueryParams, SearchRequest, ListCommitsQueryParamsSchema, ListLanguageModelsResponse } from './types.js';
44
import { isServiceError, ServiceErrorException } from './utils.js';
55
import { z } from 'zod';
66

@@ -106,4 +106,43 @@ export const listCommits = async (queryParams: ListCommitsQueryParamsSchema) =>
106106
const commits = await parseResponse(response, listCommitsResponseSchema);
107107
const totalCount = parseInt(response.headers.get('X-Total-Count') ?? '0', 10);
108108
return { commits, totalCount };
109-
}
109+
}
110+
111+
/**
112+
* Asks a natural language question about the codebase using the Sourcebot AI agent.
113+
* This is a blocking call that runs the full agent loop and returns when complete.
114+
*
115+
* @param request - The question and optional repo filters
116+
* @returns The agent's answer, chat URL, sources, and metadata
117+
*/
118+
export const askCodebase = async (request: AskCodebaseRequest): Promise<AskCodebaseResponse> => {
119+
const response = await fetch(`${env.SOURCEBOT_HOST}/api/chat/blocking`, {
120+
method: 'POST',
121+
headers: {
122+
'Content-Type': 'application/json',
123+
'X-Sourcebot-Client-Source': 'mcp',
124+
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
125+
},
126+
body: JSON.stringify(request),
127+
});
128+
129+
return parseResponse(response, askCodebaseResponseSchema);
130+
}
131+
132+
/**
133+
* Lists the available language models configured on the Sourcebot instance.
134+
*
135+
* @returns Array of language model info objects
136+
*/
137+
export const listLanguageModels = async (): Promise<ListLanguageModelsResponse> => {
138+
const response = await fetch(`${env.SOURCEBOT_HOST}/api/models`, {
139+
method: 'GET',
140+
headers: {
141+
'Content-Type': 'application/json',
142+
'X-Sourcebot-Client-Source': 'mcp',
143+
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
144+
},
145+
});
146+
147+
return parseResponse(response, listLanguageModelsResponseSchema);
148+
}

packages/mcp/src/index.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
66
import _dedent from "dedent";
77
import escapeStringRegexp from 'escape-string-regexp';
88
import { z } from 'zod';
9-
import { getFileSource, listCommits, listRepos, search } from './client.js';
9+
import { askCodebase, getFileSource, listCommits, listLanguageModels, listRepos, search } from './client.js';
1010
import { env, numberSchema } from './env.js';
11-
import { fileSourceRequestSchema, listCommitsQueryParamsSchema, listReposQueryParamsSchema } from './schemas.js';
12-
import { FileSourceRequest, ListCommitsQueryParamsSchema, ListReposQueryParams, TextContent } from './types.js';
11+
import { askCodebaseRequestSchema, fileSourceRequestSchema, listCommitsQueryParamsSchema, listReposQueryParamsSchema } from './schemas.js';
12+
import { AskCodebaseRequest, FileSourceRequest, ListCommitsQueryParamsSchema, ListReposQueryParams, TextContent } from './types.js';
1313

1414
const dedent = _dedent.withOptions({ alignValues: true });
1515

@@ -238,7 +238,57 @@ server.tool(
238238
}
239239
);
240240

241+
server.tool(
242+
"list_language_models",
243+
dedent`Lists the available language models configured on the Sourcebot instance. Use this to discover which models can be specified when calling ask_codebase.`,
244+
{},
245+
async () => {
246+
const models = await listLanguageModels();
247+
248+
return {
249+
content: [{
250+
type: "text",
251+
text: JSON.stringify(models),
252+
}],
253+
};
254+
}
255+
);
256+
257+
server.tool(
258+
"ask_codebase",
259+
dedent`
260+
Ask a natural language question about the codebase. This tool uses an AI agent to autonomously search code, read files, and find symbol references/definitions to answer your question.
261+
262+
The agent will:
263+
- Analyze your question and determine what context it needs
264+
- Search the codebase using multiple strategies (code search, symbol lookup, file reading)
265+
- Synthesize findings into a comprehensive answer with code references
241266
267+
Returns a detailed answer in markdown format with code references, plus a link to view the full research session (including all tool calls and reasoning) in the Sourcebot web UI.
268+
269+
This is a blocking operation that may take 30-60+ seconds for complex questions as the agent researches the codebase.
270+
`,
271+
askCodebaseRequestSchema.shape,
272+
async (request: AskCodebaseRequest) => {
273+
const response = await askCodebase(request);
274+
275+
// Format the response with the answer and a link to the chat
276+
const formattedResponse = dedent`
277+
${response.answer}
278+
279+
---
280+
**View full research session:** ${response.chatUrl}
281+
**Model used:** ${response.languageModel.model}
282+
`;
283+
284+
return {
285+
content: [{
286+
type: "text",
287+
text: formattedResponse,
288+
}],
289+
};
290+
}
291+
);
242292

243293
const runServer = async () => {
244294
const transport = new StdioServerTransport();

packages/mcp/src/schemas.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,45 @@ export const listCommitsResponseSchema = z.array(z.object({
272272
author_name: z.string(),
273273
author_email: z.string(),
274274
}));
275+
276+
export const languageModelInfoSchema = z.object({
277+
provider: z.string().describe("The model provider (e.g., 'anthropic', 'openai')"),
278+
model: z.string().describe("The model ID"),
279+
displayName: z.string().optional().describe("Optional display name for the model"),
280+
});
281+
282+
export const listLanguageModelsResponseSchema = z.array(languageModelInfoSchema);
283+
284+
export const askCodebaseRequestSchema = z.object({
285+
query: z
286+
.string()
287+
.describe("The query to ask about the codebase."),
288+
repos: z
289+
.array(z.string())
290+
.optional()
291+
.describe("The repositories that are accessible to the agent during the chat. If not provided, all repositories are accessible."),
292+
languageModel: languageModelInfoSchema
293+
.omit({ displayName: true })
294+
.optional()
295+
.describe("The language model to use for answering the question. If not provided, defaults to the first model in the config. Use list_language_models to see available options."),
296+
});
297+
298+
export const sourceSchema = z.object({
299+
type: z.literal('file'),
300+
repo: z.string(),
301+
path: z.string(),
302+
name: z.string(),
303+
language: z.string(),
304+
revision: z.string(),
305+
});
306+
307+
export const askCodebaseResponseSchema = z.object({
308+
answer: z.string().describe("The agent's final answer in markdown format"),
309+
chatId: z.string().describe("ID of the persisted chat session"),
310+
chatUrl: z.string().describe("URL to view the chat in the web UI"),
311+
languageModel: z.object({
312+
provider: z.string().describe("The model provider (e.g., 'anthropic', 'openai')"),
313+
model: z.string().describe("The model ID"),
314+
displayName: z.string().optional().describe("Optional display name for the model"),
315+
}).describe("The language model used to generate the response"),
316+
});

0 commit comments

Comments
 (0)