diff --git a/AGENTS.md b/AGENTS.md index 86d978856..49fa70b32 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,6 +33,10 @@ Standard dev commands are documented in `CONTRIBUTING.md` and `package.json`. Ke - **Build deps only:** `yarn build:deps` (builds shared packages: schemas, db, shared, query-language) - **DB migrations:** `yarn dev:prisma:migrate:dev` +### Deprecated Packages + +- **`packages/mcp`** - This standalone MCP package is deprecated. Do NOT modify it. MCP functionality is now handled by the web package at `packages/web/src/features/mcp/`. + ### Non-obvious Caveats - **Docker must be running** before `yarn dev`. Start it with `docker compose -f docker-compose-dev.yml up -d`. The backend will fail to connect to Redis/PostgreSQL otherwise. diff --git a/CHANGELOG.md b/CHANGELOG.md index a0350e8cd..f944ad95b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Increased `SOURCEBOT_CHAT_MAX_STEP_COUNT` default from 20 to 100 to allow agents to perform more autonomous steps. [#1017](https://github.com/sourcebot-dev/sourcebot/pull/1017) +- The `ask_codebase` MCP tool is now hidden when no language model providers are configured. [#1018](https://github.com/sourcebot-dev/sourcebot/pull/1018) ## [4.15.9] - 2026-03-17 diff --git a/packages/web/src/app/api/(server)/mcp/route.ts b/packages/web/src/app/api/(server)/mcp/route.ts index 271aff163..5db39707c 100644 --- a/packages/web/src/app/api/(server)/mcp/route.ts +++ b/packages/web/src/app/api/(server)/mcp/route.ts @@ -79,7 +79,7 @@ export const POST = apiHandler(async (request: NextRequest) => { }, }); - const mcpServer = createMcpServer(); + const mcpServer = await createMcpServer(); await mcpServer.connect(transport); return transport.handleRequest(request); diff --git a/packages/web/src/features/mcp/server.ts b/packages/web/src/features/mcp/server.ts index 3fe3c50f7..4353de0f4 100644 --- a/packages/web/src/features/mcp/server.ts +++ b/packages/web/src/features/mcp/server.ts @@ -1,5 +1,5 @@ import { listRepos } from '@/app/api/(server)/repos/listReposApi'; -import { getConfiguredLanguageModelsInfo } from "../chat/utils.server"; +import { getConfiguredLanguageModels, getConfiguredLanguageModelsInfo } from "../chat/utils.server"; import { askCodebase } from '@/features/mcp/askCodebase'; import { languageModelInfoSchema, @@ -66,12 +66,15 @@ const TOOL_DESCRIPTIONS = { `, }; -export function createMcpServer(): McpServer { +export async function createMcpServer(): Promise { const server = new McpServer({ name: 'sourcebot-mcp-server', version: SOURCEBOT_VERSION, }); + const configuredModels = await getConfiguredLanguageModels(); + const hasLanguageModels = configuredModels.length > 0; + server.registerTool( "search_code", { @@ -493,43 +496,45 @@ export function createMcpServer(): McpServer { } ); - server.registerTool( - "ask_codebase", - { - description: TOOL_DESCRIPTIONS.ask_codebase, - annotations: { readOnlyHint: true }, - inputSchema: z.object({ - query: z.string().describe("The query to ask about the codebase."), - repos: z.array(z.string()).optional().describe("The repositories accessible to the agent. If not provided, all repositories are accessible."), - languageModel: languageModelInfoSchema.optional().describe("The language model to use. If not provided, defaults to the first model in the config."), - visibility: z.enum(['PRIVATE', 'PUBLIC']).optional().describe("The visibility of the chat session. Defaults to PRIVATE for authenticated users."), - }), - }, - async (request) => { - const result = await askCodebase({ - query: request.query, - repos: request.repos, - languageModel: request.languageModel, - visibility: request.visibility as ChatVisibility | undefined, - source: 'mcp', - }); + if (hasLanguageModels) { + server.registerTool( + "ask_codebase", + { + description: TOOL_DESCRIPTIONS.ask_codebase, + annotations: { readOnlyHint: true }, + inputSchema: z.object({ + query: z.string().describe("The query to ask about the codebase."), + repos: z.array(z.string()).optional().describe("The repositories accessible to the agent. If not provided, all repositories are accessible."), + languageModel: languageModelInfoSchema.optional().describe("The language model to use. If not provided, defaults to the first model in the config."), + visibility: z.enum(['PRIVATE', 'PUBLIC']).optional().describe("The visibility of the chat session. Defaults to PRIVATE for authenticated users."), + }), + }, + async (request) => { + const result = await askCodebase({ + query: request.query, + repos: request.repos, + languageModel: request.languageModel, + visibility: request.visibility as ChatVisibility | undefined, + source: 'mcp', + }); - if (isServiceError(result)) { - return { - content: [{ type: "text", text: `Failed to ask codebase: ${result.message}` }], - }; - } + if (isServiceError(result)) { + return { + content: [{ type: "text", text: `Failed to ask codebase: ${result.message}` }], + }; + } - const formattedResponse = dedent` - ${result.answer} + const formattedResponse = dedent` + ${result.answer} - --- - **View full research session:** ${result.chatUrl} - **Model used:** ${result.languageModel.model} - `; - return { content: [{ type: "text", text: formattedResponse }] }; - } - ); + --- + **View full research session:** ${result.chatUrl} + **Model used:** ${result.languageModel.model} + `; + return { content: [{ type: "text", text: formattedResponse }] }; + } + ); + } return server; }