Skip to content

Commit 59c6ebb

Browse files
wip
1 parent 97f2810 commit 59c6ebb

File tree

7 files changed

+532
-11
lines changed

7 files changed

+532
-11
lines changed

packages/mcp/src/client.ts

Lines changed: 22 additions & 2 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 } from './schemas.js';
3+
import { AskCodebaseRequest, AskCodebaseResponse, FileSourceRequest, ListReposQueryParams, SearchRequest, ListCommitsQueryParamsSchema } from './types.js';
44
import { isServiceError, ServiceErrorException } from './utils.js';
55
import { z } from 'zod';
66

@@ -103,3 +103,23 @@ export const listCommits = async (queryParams: ListCommitsQueryParamsSchema) =>
103103
const totalCount = parseInt(response.headers.get('X-Total-Count') ?? '0', 10);
104104
return { commits, totalCount };
105105
}
106+
107+
/**
108+
* Asks a natural language question about the codebase using the Sourcebot AI agent.
109+
* This is a blocking call that runs the full agent loop and returns when complete.
110+
*
111+
* @param request - The question and optional repo filters
112+
* @returns The agent's answer, chat URL, sources, and metadata
113+
*/
114+
export const askCodebase = async (request: AskCodebaseRequest): Promise<AskCodebaseResponse> => {
115+
const response = await fetch(`${env.SOURCEBOT_HOST}/api/chat/blocking`, {
116+
method: 'POST',
117+
headers: {
118+
'Content-Type': 'application/json',
119+
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
120+
},
121+
body: JSON.stringify(request),
122+
});
123+
124+
return parseResponse(response, askCodebaseResponseSchema);
125+
}

packages/mcp/src/index.ts

Lines changed: 49 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, 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

@@ -239,7 +239,53 @@ server.tool(
239239
}
240240
);
241241

242+
server.tool(
243+
"ask_codebase",
244+
dedent`
245+
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.
246+
247+
The agent will:
248+
- Analyze your question and determine what context it needs
249+
- Search the codebase using multiple strategies (code search, symbol lookup, file reading)
250+
- Synthesize findings into a comprehensive answer with code references
251+
252+
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.
253+
254+
This is a blocking operation that may take 30-60+ seconds for complex questions as the agent researches the codebase.
255+
`,
256+
{
257+
question: z.string().describe("The question to ask about the codebase."),
258+
repo: z.string().describe("The repository to ask the question on."),
259+
},
260+
async ({
261+
question,
262+
repo,
263+
}) => {
264+
const response = await askCodebase({
265+
question,
266+
repos: [repo],
267+
});
242268

269+
// Format the response with the answer and a link to the chat
270+
const formattedResponse = dedent`
271+
${response.answer}
272+
273+
---
274+
**View full research session:** ${response.chatUrl}
275+
276+
**Sources referenced:** ${response.sources.length} files
277+
**Response time:** ${(response.metadata.totalResponseTimeMs / 1000).toFixed(1)}s
278+
**Model:** ${response.metadata.modelName}
279+
`;
280+
281+
return {
282+
content: [{
283+
type: "text",
284+
text: formattedResponse,
285+
}],
286+
};
287+
}
288+
);
243289

244290
const runServer = async () => {
245291
const transport = new StdioServerTransport();

packages/mcp/src/schemas.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,35 @@ export const listCommitsResponseSchema = z.array(z.object({
273273
author_name: z.string(),
274274
author_email: z.string(),
275275
}));
276+
277+
// ============================================================================
278+
// Ask Codebase (Blocking Chat API)
279+
// ============================================================================
280+
281+
export const askCodebaseRequestSchema = z.object({
282+
question: z.string().describe("The question to ask about the codebase"),
283+
repos: z.array(z.string()).optional().describe("Optional: filter to specific repositories by name"),
284+
});
285+
286+
export const sourceSchema = z.object({
287+
type: z.literal('file'),
288+
repo: z.string(),
289+
path: z.string(),
290+
name: z.string(),
291+
language: z.string(),
292+
revision: z.string(),
293+
});
294+
295+
export const askCodebaseResponseSchema = z.object({
296+
answer: z.string().describe("The agent's final answer in markdown format"),
297+
chatId: z.string().describe("ID of the persisted chat session"),
298+
chatUrl: z.string().describe("URL to view the chat in the web UI"),
299+
sources: z.array(sourceSchema).describe("Files the agent referenced during research"),
300+
metadata: z.object({
301+
totalTokens: z.number(),
302+
inputTokens: z.number(),
303+
outputTokens: z.number(),
304+
totalResponseTimeMs: z.number(),
305+
modelName: z.string(),
306+
}).describe("Metadata about the response"),
307+
});

packages/mcp/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
serviceErrorSchema,
1313
listCommitsQueryParamsSchema,
1414
listCommitsResponseSchema,
15+
askCodebaseRequestSchema,
16+
askCodebaseResponseSchema,
1517
} from "./schemas.js";
1618
import { z } from "zod";
1719

@@ -34,3 +36,6 @@ export type ServiceError = z.infer<typeof serviceErrorSchema>;
3436

3537
export type ListCommitsQueryParamsSchema = z.infer<typeof listCommitsQueryParamsSchema>;
3638
export type ListCommitsResponse = z.infer<typeof listCommitsResponseSchema>;
39+
40+
export type AskCodebaseRequest = z.infer<typeof askCodebaseRequestSchema>;
41+
export type AskCodebaseResponse = z.infer<typeof askCodebaseResponseSchema>;

0 commit comments

Comments
 (0)