Skip to content

Commit c6b4ef1

Browse files
cursoragentmsukkari
andcommitted
feat(mcp): hide ask_codebase tool when no LLM providers configured
- Modified web MCP server to check configured language models before registering the ask_codebase tool - Made createMcpServer() async to support awaiting the config check - Updated standalone MCP package to check /api/models before registering ask_codebase tool - Tool is now only visible when at least one language model is configured Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com>
1 parent 39145fb commit c6b4ef1

File tree

3 files changed

+86
-75
lines changed

3 files changed

+86
-75
lines changed

packages/mcp/src/index.ts

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,8 @@ import { buildTreeNodeIndex, joinTreePath, normalizeTreePath, sortTreeEntries }
1414

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

17-
// Create MCP server
18-
const server = new McpServer({
19-
name: 'sourcebot-mcp-server',
20-
version: '0.1.0',
21-
});
22-
23-
24-
server.tool(
17+
function registerTools(server: McpServer, hasLanguageModels: boolean) {
18+
server.tool(
2519
"search_code",
2620
dedent`
2721
Searches for code that matches the provided search query as a substring by default, or as a regular expression if useRegex is true. Useful for exploring remote repositories by searching for exact symbols, functions, variables, or specific code patterns. To determine if a repository is indexed, use the \`list_repos\` tool. By default, searches are global and will search the default branch of all repositories. Searches can be scoped to specific repositories, languages, and branches. When referencing code outputted by this tool, always include the file's external URL as a link. This makes it easier for the user to view the file, even if they don't have it locally checked out.
@@ -413,46 +407,58 @@ server.tool(
413407
}
414408
);
415409

416-
server.tool(
417-
"ask_codebase",
418-
dedent`
419-
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.
410+
if (hasLanguageModels) {
411+
server.tool(
412+
"ask_codebase",
413+
dedent`
414+
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.
420415
421-
The agent will:
422-
- Analyze your question and determine what context it needs
423-
- Search the codebase using multiple strategies (code search, symbol lookup, file reading)
424-
- Synthesize findings into a comprehensive answer with code references
416+
The agent will:
417+
- Analyze your question and determine what context it needs
418+
- Search the codebase using multiple strategies (code search, symbol lookup, file reading)
419+
- Synthesize findings into a comprehensive answer with code references
425420
426-
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.
421+
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.
427422
428-
When using this in shared environments (e.g., Slack), you can set the visibility parameter to 'PUBLIC' to ensure everyone can access the chat link.
423+
When using this in shared environments (e.g., Slack), you can set the visibility parameter to 'PUBLIC' to ensure everyone can access the chat link.
429424
430-
This is a blocking operation that may take 30-60+ seconds for complex questions as the agent researches the codebase.
431-
`,
432-
askCodebaseRequestSchema.shape,
433-
{ readOnlyHint: true },
434-
async (request: AskCodebaseRequest) => {
435-
const response = await askCodebase(request);
425+
This is a blocking operation that may take 30-60+ seconds for complex questions as the agent researches the codebase.
426+
`,
427+
askCodebaseRequestSchema.shape,
428+
{ readOnlyHint: true },
429+
async (request: AskCodebaseRequest) => {
430+
const response = await askCodebase(request);
436431

437-
// Format the response with the answer and a link to the chat
438-
const formattedResponse = dedent`
439-
${response.answer}
432+
// Format the response with the answer and a link to the chat
433+
const formattedResponse = dedent`
434+
${response.answer}
440435
441-
---
442-
**View full research session:** ${response.chatUrl}
443-
**Model used:** ${response.languageModel.model}
444-
`;
436+
---
437+
**View full research session:** ${response.chatUrl}
438+
**Model used:** ${response.languageModel.model}
439+
`;
445440

446-
return {
447-
content: [{
448-
type: "text",
449-
text: formattedResponse,
450-
}],
451-
};
441+
return {
442+
content: [{
443+
type: "text",
444+
text: formattedResponse,
445+
}],
446+
};
447+
}
448+
);
452449
}
453-
);
450+
}
454451

455452
const runServer = async () => {
453+
const server = new McpServer({
454+
name: 'sourcebot-mcp-server',
455+
version: '0.1.0',
456+
});
457+
458+
const models = await listLanguageModels();
459+
const hasLanguageModels = models.length > 0;
460+
registerTools(server, hasLanguageModels);
461+
456462
const transport = new StdioServerTransport();
457463
await server.connect(transport);
458464
}

packages/web/src/app/api/(server)/mcp/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
7979
},
8080
});
8181

82-
const mcpServer = createMcpServer();
82+
const mcpServer = await createMcpServer();
8383
await mcpServer.connect(transport);
8484

8585
return transport.handleRequest(request);

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

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { listRepos } from '@/app/api/(server)/repos/listReposApi';
2-
import { getConfiguredLanguageModelsInfo } from "../chat/utils.server";
2+
import { getConfiguredLanguageModels, getConfiguredLanguageModelsInfo } from "../chat/utils.server";
33
import { askCodebase } from '@/features/mcp/askCodebase';
44
import {
55
languageModelInfoSchema,
@@ -66,12 +66,15 @@ const TOOL_DESCRIPTIONS = {
6666
`,
6767
};
6868

69-
export function createMcpServer(): McpServer {
69+
export async function createMcpServer(): Promise<McpServer> {
7070
const server = new McpServer({
7171
name: 'sourcebot-mcp-server',
7272
version: SOURCEBOT_VERSION,
7373
});
7474

75+
const configuredModels = await getConfiguredLanguageModels();
76+
const hasLanguageModels = configuredModels.length > 0;
77+
7578
server.registerTool(
7679
"search_code",
7780
{
@@ -493,43 +496,45 @@ export function createMcpServer(): McpServer {
493496
}
494497
);
495498

496-
server.registerTool(
497-
"ask_codebase",
498-
{
499-
description: TOOL_DESCRIPTIONS.ask_codebase,
500-
annotations: { readOnlyHint: true },
501-
inputSchema: z.object({
502-
query: z.string().describe("The query to ask about the codebase."),
503-
repos: z.array(z.string()).optional().describe("The repositories accessible to the agent. If not provided, all repositories are accessible."),
504-
languageModel: languageModelInfoSchema.optional().describe("The language model to use. If not provided, defaults to the first model in the config."),
505-
visibility: z.enum(['PRIVATE', 'PUBLIC']).optional().describe("The visibility of the chat session. Defaults to PRIVATE for authenticated users."),
506-
}),
507-
},
508-
async (request) => {
509-
const result = await askCodebase({
510-
query: request.query,
511-
repos: request.repos,
512-
languageModel: request.languageModel,
513-
visibility: request.visibility as ChatVisibility | undefined,
514-
source: 'mcp',
515-
});
499+
if (hasLanguageModels) {
500+
server.registerTool(
501+
"ask_codebase",
502+
{
503+
description: TOOL_DESCRIPTIONS.ask_codebase,
504+
annotations: { readOnlyHint: true },
505+
inputSchema: z.object({
506+
query: z.string().describe("The query to ask about the codebase."),
507+
repos: z.array(z.string()).optional().describe("The repositories accessible to the agent. If not provided, all repositories are accessible."),
508+
languageModel: languageModelInfoSchema.optional().describe("The language model to use. If not provided, defaults to the first model in the config."),
509+
visibility: z.enum(['PRIVATE', 'PUBLIC']).optional().describe("The visibility of the chat session. Defaults to PRIVATE for authenticated users."),
510+
}),
511+
},
512+
async (request) => {
513+
const result = await askCodebase({
514+
query: request.query,
515+
repos: request.repos,
516+
languageModel: request.languageModel,
517+
visibility: request.visibility as ChatVisibility | undefined,
518+
source: 'mcp',
519+
});
516520

517-
if (isServiceError(result)) {
518-
return {
519-
content: [{ type: "text", text: `Failed to ask codebase: ${result.message}` }],
520-
};
521-
}
521+
if (isServiceError(result)) {
522+
return {
523+
content: [{ type: "text", text: `Failed to ask codebase: ${result.message}` }],
524+
};
525+
}
522526

523-
const formattedResponse = dedent`
524-
${result.answer}
527+
const formattedResponse = dedent`
528+
${result.answer}
525529
526-
---
527-
**View full research session:** ${result.chatUrl}
528-
**Model used:** ${result.languageModel.model}
529-
`;
530-
return { content: [{ type: "text", text: formattedResponse }] };
531-
}
532-
);
530+
---
531+
**View full research session:** ${result.chatUrl}
532+
**Model used:** ${result.languageModel.model}
533+
`;
534+
return { content: [{ type: "text", text: formattedResponse }] };
535+
}
536+
);
537+
}
533538

534539
return server;
535540
}

0 commit comments

Comments
 (0)