feat(web): add MCP HTTP endpoint with Streamable HTTP transport#976
feat(web): add MCP HTTP endpoint with Streamable HTTP transport#976brendan-kellam merged 5 commits intomainfrom
Conversation
…263) - Add /api/mcp route with WebStandardStreamableHTTPServerTransport supporting SSE and JSON responses - Add Bearer token auth support to getAuthenticatedUser for programmatic MCP clients - Add session ownership validation to prevent session hijacking (per MCP security spec) - Extract chat utils to utils.server.ts to fix 'use server' module boundary violations - Add ask_codebase, search_code, read_file, list_repos, list_commits, list_tree, list_language_models MCP tools - Add withAuthV2 tests for Bearer token authentication flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
|
Caution Review failedPull request was closed or merged during review Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds Model Context Protocol (MCP) support and a new askCodebase orchestrator, extracts server-side chat utilities and multi-provider LLM initialization, refactors chat streaming to createMessageStream, adds Bearer-token auth handling, and introduces dependency Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Client as Web Client
participant MCP as /api/mcp
participant Orchestrator as askCodebase
participant Search as Search Service
participant LLM as Language Model / Agent
participant DB as Chat DB
User->>Client: Submit query (query, repos, model)
Client->>MCP: POST /api/mcp (ask_codebase)
MCP->>Orchestrator: askCodebase(params)
Orchestrator->>Orchestrator: validate configured models
Orchestrator->>DB: create chat record
Orchestrator->>Search: resolve repos -> search scopes
Orchestrator->>LLM: start createMessageStream
LLM->>Search: execute repo searches
Search-->>LLM: return results
LLM-->>Orchestrator: stream message chunks
Orchestrator->>DB: updateChatMessages(onFinish)
Orchestrator->>Orchestrator: generate chat name
Orchestrator->>DB: update chat name & metadata
Orchestrator-->>MCP: return answer, chatId, chatUrl, modelInfo
MCP-->>Client: response (markdown answer + link)
Client-->>User: display answer and session link
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/chat/blocking/route.ts (1)
14-29:⚠️ Potential issue | 🟠 MajorAvoid request-schema drift with MCP package contract.
This route now owns a local blocking schema, but it differs from the MCP-side
askCodebaseRequestSchema(packages/mcp/src/schemas.ts), which increases integration drift risk over time. Prefer deriving from a shared schema (or matching it exactly) so/api/chat/blockingand MCP tools stay contract-compatible.🔧 Minimal alignment patch
- languageModel: languageModelInfoSchema - .optional() + languageModel: languageModelInfoSchema + .omit({ displayName: true }) + .optional() .describe("The language model to use for the chat. If not provided, the first configured model is used."), - visibility: z - .nativeEnum(ChatVisibility) + visibility: z + .enum(['PRIVATE', 'PUBLIC']) .optional()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/app/api/`(server)/chat/blocking/route.ts around lines 14 - 29, The local blockingChatRequestSchema diverges from the MCP-side askCodebaseRequestSchema; update this route to reuse the shared contract instead of duplicating it: import and use askCodebaseRequestSchema (or export a shared schema from the MCP package) and replace blockingChatRequestSchema with that imported schema (or derive from it, e.g., z.intersection/extend if you need small local additions), making sure fields like query, repos, languageModel, and visibility keep identical types/optional flags and descriptions to avoid schema drift.
🧹 Nitpick comments (3)
packages/web/src/withAuthV2.ts (1)
119-122: Handle theAuthorizationscheme case-insensitively for broader client compatibility.
startsWith("Bearer ")is strict; some clients sendbearer .... A case-insensitive parse avoids false auth misses.♻️ Proposed adjustment
- const authorizationHeader = (await headers()).get("Authorization") ?? undefined; - if (authorizationHeader?.startsWith("Bearer ")) { - const bearerToken = authorizationHeader.slice(7); + const authorizationHeader = (await headers()).get("Authorization") ?? undefined; + const bearerMatch = authorizationHeader?.match(/^Bearer\s+(.+)$/i); + if (bearerMatch) { + const bearerToken = bearerMatch[1].trim(); + if (!bearerToken) { + // continue to other auth methods + } else { const apiKey = await getVerifiedApiObject(bearerToken); if (apiKey) { const user = await __unsafePrisma.user.findUnique({ where: { id: apiKey.createdById }, include: { accounts: true }, }); if (user) { await __unsafePrisma.apiKey.update({ where: { hash: apiKey.hash }, data: { lastUsedAt: new Date() }, }); return user; } } + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/withAuthV2.ts` around lines 119 - 122, The Authorization header check using authorizationHeader?.startsWith("Bearer ") is case-sensitive and can miss valid tokens; update the logic around headers(), authorizationHeader, bearerToken and getVerifiedApiObject to perform a case-insensitive scheme check (e.g., normalize authorizationHeader to lowercase or use a case-insensitive regex) so you detect "Bearer" regardless of case, then extract the token after the scheme and pass it to getVerifiedApiObject.packages/web/src/features/chat/actions.ts (1)
513-516: Use the same owner helper in feedback authorization.Line 515 still uses
chat.createdById !== user?.iddirectly, while other flows now rely onisOwnerOfChat. Reusing the helper here prevents ownership-rule drift.♻️ Suggested consistency fix
- const isSharedWithUser = await isChatSharedWithUser({ prisma, chatId, userId: user?.id }); - if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== user?.id && !isSharedWithUser) { + const isOwner = await isOwnerOfChat(chat, user); + const isSharedWithUser = await isChatSharedWithUser({ prisma, chatId, userId: user?.id }); + if (chat.visibility === ChatVisibility.PRIVATE && !isOwner && !isSharedWithUser) { return notFound(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/features/chat/actions.ts` around lines 513 - 516, Replace the direct owner check in the feedback authorization branch with the shared owner helper: instead of comparing chat.createdById !== user?.id use the isOwnerOfChat helper (call isOwnerOfChat({ prisma, chatId, userId: user?.id })) so the condition becomes "chat.visibility === ChatVisibility.PRIVATE && !isOwnerOfChat(...) && !isChatSharedWithUser(...)" ensuring ownership logic is centralized and consistent with other flows.packages/web/src/features/mcp/server.ts (1)
356-369: Use an index-based queue to avoidshift()costs in BFS.Line 367-368 repeatedly calls
queue.shift(), which reindexes the array each time. For larger trees, this is an avoidable hot-path cost.⚡ Suggested queue pattern
- const queue: Array<{ path: string; depth: number }> = [{ path: normalizedPath, depth: 0 }]; + const queue: Array<{ path: string; depth: number }> = [{ path: normalizedPath, depth: 0 }]; + let queueHead = 0; - while (queue.length > 0 && !truncated) { - const currentDepth = queue[0]!.depth; + while (queueHead < queue.length && !truncated) { + const currentDepth = queue[queueHead]!.depth; const currentLevelPaths: string[] = []; - while (queue.length > 0 && queue[0]!.depth === currentDepth) { - currentLevelPaths.push(queue.shift()!.path); + while (queueHead < queue.length && queue[queueHead]!.depth === currentDepth) { + currentLevelPaths.push(queue[queueHead]!.path); + queueHead++; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/features/mcp/server.ts` around lines 356 - 369, The BFS loop uses array.shift() on the variable queue (declared as const queue: Array<{ path: string; depth: number }>) which causes O(n) reindexing; replace the shifting pattern with an index-based queue (e.g., maintain a head numeric pointer) so you iterate by advancing head and push new items to queue without shifting. Update the two while loops that rely on queue.shift() (the outer while (queue.length > 0 && !truncated) and the inner grouping loop that builds currentLevelPaths) to use the head pointer and queue[head] accesses, and ensure queuedPaths, seenEntries, entries, truncated and treeError logic remains unchanged. Make sure to stop the loop when head >= queue.length or truncated becomes true and preserve depth grouping by reading queue[head].depth into currentDepth as done today.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/app/`[domain]/askgh/[owner]/[repo]/page.tsx:
- Around line 48-53: The code calls getConfiguredLanguageModelsInfo() before
checking repoInfo for errors, which can do unnecessary work or surface unrelated
failures; reorder and validate repoInfo returned by getRepoInfo() first using
isServiceError(repoInfo) and throw new ServiceErrorException(repoInfo)
immediately if it is an error, only then call getConfiguredLanguageModelsInfo()
and proceed—update the sequence around the repoInfo variable and the
getConfiguredLanguageModelsInfo() invocation so the error guard (isServiceError
and ServiceErrorException) runs before loading language models.
In `@packages/web/src/app/api/`(server)/mcp/route.ts:
- Around line 23-25: The module-level unbounded sessions Map (sessions of type
Map<string, McpSession>) can grow without limit; add a MAX_SESSIONS constant and
bound the store by evicting oldest entries when inserting new sessions (use Map
insertion order to find/delete the first key) and/or add a TTL on McpSession
(e.g., lastAccess timestamp) with a periodic cleanup task; update every place
that calls sessions.set (and any session-creation code) to perform the eviction
check before adding and update lastAccess on reads so stale sessions can be
removed.
- Around line 31-32: Validate the MCP-Session-Id header with a Zod schema before
any lookup/creation: replace direct reads of
request.headers.get(MCP_SESSION_ID_HEADER) with validated value using a
z.string().uuid() (or appropriate pattern) schema and return a
400/Response.badRequest when parsing fails; apply this validation wherever
sessionId is consumed in route.ts (the places that currently assign to sessionId
around the MCP_SESSION_ID_HEADER reads and any subsequent session
lookup/creation logic) so downstream code never operates on a malformed
identifier.
In `@packages/web/src/features/chat/agent.ts`:
- Around line 69-87: The current messages.map callback in the module uses
conditional returns and sometimes falls through without returning, triggering
the useIterableCallbackReturn lint error; replace the map+filter with
messages.flatMap(...) so the callback always returns an array: return [] when no
value should be emitted, or return [{ role: 'user', content: ... }] or [{ role:
'assistant', content: [answerPart] }] when producing an item, using the same
logic around message.role and getAnswerPartFromAssistantMessage to locate the
correct branch and ensure no undefined values remain.
In `@packages/web/src/features/mcp/askCodebase.ts`:
- Around line 78-85: The chat record is created with prisma.chat.create before
repo validation runs and ServiceErrorException can be thrown, causing orphan
chats on invalid input; move the repo validation logic (the code that can throw
ServiceErrorException for invalid repos) to run before calling
prisma.chat.create so you only persist the chat after repos are confirmed valid,
and if validation fails do not call prisma.chat.create (ensure the same data
fields like orgId, createdById, visibility, messages remain used when creating
the chat after validation).
- Around line 105-107: The repo lookup using prisma.repo.findFirst (in
askCodebase.ts) filters only by name, allowing cross-org access; update the
where clause passed to prisma.repo.findFirst (the repoDB lookup) to include
orgId: org.id (using the org from withOptionalAuthV2) so the query becomes
scoped to the current organization.
---
Outside diff comments:
In `@packages/web/src/app/api/`(server)/chat/blocking/route.ts:
- Around line 14-29: The local blockingChatRequestSchema diverges from the
MCP-side askCodebaseRequestSchema; update this route to reuse the shared
contract instead of duplicating it: import and use askCodebaseRequestSchema (or
export a shared schema from the MCP package) and replace
blockingChatRequestSchema with that imported schema (or derive from it, e.g.,
z.intersection/extend if you need small local additions), making sure fields
like query, repos, languageModel, and visibility keep identical types/optional
flags and descriptions to avoid schema drift.
---
Nitpick comments:
In `@packages/web/src/features/chat/actions.ts`:
- Around line 513-516: Replace the direct owner check in the feedback
authorization branch with the shared owner helper: instead of comparing
chat.createdById !== user?.id use the isOwnerOfChat helper (call isOwnerOfChat({
prisma, chatId, userId: user?.id })) so the condition becomes "chat.visibility
=== ChatVisibility.PRIVATE && !isOwnerOfChat(...) && !isChatSharedWithUser(...)"
ensuring ownership logic is centralized and consistent with other flows.
In `@packages/web/src/features/mcp/server.ts`:
- Around line 356-369: The BFS loop uses array.shift() on the variable queue
(declared as const queue: Array<{ path: string; depth: number }>) which causes
O(n) reindexing; replace the shifting pattern with an index-based queue (e.g.,
maintain a head numeric pointer) so you iterate by advancing head and push new
items to queue without shifting. Update the two while loops that rely on
queue.shift() (the outer while (queue.length > 0 && !truncated) and the inner
grouping loop that builds currentLevelPaths) to use the head pointer and
queue[head] accesses, and ensure queuedPaths, seenEntries, entries, truncated
and treeError logic remains unchanged. Make sure to stop the loop when head >=
queue.length or truncated becomes true and preserve depth grouping by reading
queue[head].depth into currentDepth as done today.
In `@packages/web/src/withAuthV2.ts`:
- Around line 119-122: The Authorization header check using
authorizationHeader?.startsWith("Bearer ") is case-sensitive and can miss valid
tokens; update the logic around headers(), authorizationHeader, bearerToken and
getVerifiedApiObject to perform a case-insensitive scheme check (e.g., normalize
authorizationHeader to lowercase or use a case-insensitive regex) so you detect
"Bearer" regardless of case, then extract the token after the scheme and pass it
to getVerifiedApiObject.
ℹ️ Review info
Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d59faa7e-6e2a-4a29-8e24-530d66940848
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (22)
packages/web/package.jsonpackages/web/src/app/[domain]/askgh/[owner]/[repo]/page.tsxpackages/web/src/app/[domain]/browse/layout.tsxpackages/web/src/app/[domain]/chat/[id]/page.tsxpackages/web/src/app/[domain]/chat/page.tsxpackages/web/src/app/[domain]/search/page.tsxpackages/web/src/app/api/(server)/chat/blocking/route.tspackages/web/src/app/api/(server)/chat/route.tspackages/web/src/app/api/(server)/mcp/route.tspackages/web/src/app/api/(server)/models/route.tspackages/web/src/features/chat/actions.tspackages/web/src/features/chat/agent.tspackages/web/src/features/chat/types.tspackages/web/src/features/chat/utils.server.tspackages/web/src/features/chat/utils.tspackages/web/src/features/mcp/askCodebase.tspackages/web/src/features/mcp/server.tspackages/web/src/features/mcp/types.tspackages/web/src/features/mcp/utils.tspackages/web/src/features/searchAssist/actions.tspackages/web/src/withAuthV2.test.tspackages/web/src/withAuthV2.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/app/api/`(server)/mcp/route.ts:
- Around line 29-42: The current ownership check lets all unauthenticated
requests share ownerId = null and access sessions; change the logic in the route
that uses ownerId, sessions, MCP_SESSION_ID_HEADER, session.ownerId and
transport.handleRequest so unauthenticated requests cannot match null ownerId.
Two concise fixes: (A) Preferred: reject requests with no authenticated user (if
auth is required) by returning the ServiceError when user is falsy before any
session lookup; or (B) If anonymous sessions are allowed, require a
client-provided anonymous identifier (e.g., read X-Anonymous-Id header), store
it as session.anonymousId when creating sessions, and on access require
session.anonymousId === requestAnonymousId instead of comparing null ownerId.
Implement one of these in the code paths that currently compare session.ownerId
!== ownerId and in the session-creation/lookup branches referenced in this file.
- Around line 46-57: The onsessionclosed handler currently only removes the
session from the sessions map, leaking resources; update the onsessionclosed
implementation to retrieve the stored session entry (from sessions by
closedSessionId) and asynchronously call the session's server.close() and
transport.close() (handle/await errors), then delete the session; ensure the
session was created with createMcpServer() and
WebStandardStreamableHTTPServerTransport so you reference the same mcpServer and
transport instances stored in sessions when performing the cleanup.
ℹ️ Review info
Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ea6f37c3-c91d-405c-9e07-18508e38804d
📒 Files selected for processing (3)
packages/web/src/app/api/(server)/mcp/route.tspackages/web/src/lib/apiHandler.tspackages/web/src/lib/posthogEvents.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
CHANGELOG.md (1)
11-11: Fix wording/formatting in changelog entry text.Line 11 has a small grammar/style issue (
support aand lowercasehttp), which makes the entry read awkwardly.✏️ Suggested edit
-- Added support a MCP streamable http transport hosted at `/api/mcp`. [`#976`](https://github.com/sourcebot-dev/sourcebot/pull/976) +- Added support for an MCP streamable HTTP transport hosted at `/api/mcp`. [`#976`](https://github.com/sourcebot-dev/sourcebot/pull/976)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CHANGELOG.md` at line 11, Update the changelog entry sentence "Added support a MCP streamable http transport hosted at `/api/mcp`." to correct grammar and capitalization: change "support a" to "support for an" and "http" to "HTTP" so it reads e.g. "Added support for an MCP streamable HTTP transport hosted at `/api/mcp`."; modify the exact line containing that sentence in CHANGELOG.md.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@CHANGELOG.md`:
- Line 11: Update the changelog entry sentence "Added support a MCP streamable
http transport hosted at `/api/mcp`." to correct grammar and capitalization:
change "support a" to "support for an" and "http" to "HTTP" so it reads e.g.
"Added support for an MCP streamable HTTP transport hosted at `/api/mcp`.";
modify the exact line containing that sentence in CHANGELOG.md.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/web/src/features/mcp/askCodebase.ts (1)
78-124:⚠️ Potential issue | 🟠 MajorValidate repos before creating the chat record to avoid orphan chats.
The chat record is created at lines 78-85, but repo validation at lines 104-124 can throw
ServiceErrorExceptionfor invalid repos. This leaves orphan chat records in the database when validation fails.Move the repo validation block before the chat creation:
🐛 Suggested reorder
+ const selectedRepos = (await Promise.all(repos.map(async (repo) => { + const repoDB = await prisma.repo.findFirst({ + where: { + name: repo, + orgId: org.id, + }, + }); + if (!repoDB) { + throw new ServiceErrorException({ + statusCode: StatusCodes.BAD_REQUEST, + errorCode: ErrorCode.INVALID_REQUEST_BODY, + message: `Repository '${repo}' not found.`, + }); + } + return { + type: 'repo', + value: repoDB.name, + name: repoDB.displayName ?? repoDB.name.split('/').pop() ?? repoDB.name, + codeHostType: repoDB.external_codeHostType, + } satisfies SearchScope; + }))); + const chat = await prisma.chat.create({ data: { orgId: org.id, createdById: user?.id, visibility: chatVisibility, messages: [] as unknown as Prisma.InputJsonValue, }, }); await captureEvent('wa_chat_thread_created', { chatId: chat.id, isAnonymous: !user, }); - - // ... (logging and userMessage creation remain here) ... - - const selectedRepos = (await Promise.all(repos.map(async (repo) => { - const repoDB = await prisma.repo.findFirst({ - where: { - name: repo, - orgId: org.id, - }, - }); - if (!repoDB) { - throw new ServiceErrorException({ - statusCode: StatusCodes.BAD_REQUEST, - errorCode: ErrorCode.INVALID_REQUEST_BODY, - message: `Repository '${repo}' not found.`, - }); - } - return { - type: 'repo', - value: repoDB.name, - name: repoDB.displayName ?? repoDB.name.split('/').pop() ?? repoDB.name, - codeHostType: repoDB.external_codeHostType, - } satisfies SearchScope; - })));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/features/mcp/askCodebase.ts` around lines 78 - 124, The chat record is created with prisma.chat.create before validating repos, so invalid repo names can throw ServiceErrorException (from the repos map block) and leave orphan chats; move the repo validation/lookup (the Promise.all over repos that queries prisma.repo.findFirst and throws ServiceErrorException) to run before calling prisma.chat.create, then use the validated selectedRepos when creating/continuing the chat and only call captureEvent('wa_chat_thread_created', ...) and start the agent after validation succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/mcp/askCodebase.ts`:
- Around line 163-169: The Promise.all combining blockStreamUntilFinish(stream)
and generateChatNameFromMessage(...) can reject if generateChatNameFromMessage
throws, preventing updateChatMessages from running; modify the logic so you
await blockStreamUntilFinish(stream) independently and call
generateChatNameFromMessage in a try/catch (or use .catch) to fall back to a
default name on error, then call updateChatMessages with the streamed messages
and the determined name; specifically change the call that uses Promise.all to
first await blockStreamUntilFinish(stream) and then safely obtain name via
generateChatNameFromMessage with error handling, referencing the functions
blockStreamUntilFinish, generateChatNameFromMessage, and updateChatMessages.
- Around line 147-149: The onFinish callback passed to createMessageStream is
async but not awaited, causing a race where blockStreamUntilFinish can finish
before finalMessages is populated; fix this by making onFinish return or resolve
a Promise that you await after blockStreamUntilFinish (or by awaiting the
createMessageStream/reader completion that includes onFinish). Concretely: in
the stream setup around createMessageStream and blockStreamUntilFinish, change
the onFinish handler (currently setting finalMessages) to resolve an externally
created Promise (e.g., finishPromise) and then await that finishPromise after
blockStreamUntilFinish completes so finalMessages is guaranteed populated before
you use/save it; reference createMessageStream, onFinish,
blockStreamUntilFinish, and finalMessages to locate the changes.
---
Duplicate comments:
In `@packages/web/src/features/mcp/askCodebase.ts`:
- Around line 78-124: The chat record is created with prisma.chat.create before
validating repos, so invalid repo names can throw ServiceErrorException (from
the repos map block) and leave orphan chats; move the repo validation/lookup
(the Promise.all over repos that queries prisma.repo.findFirst and throws
ServiceErrorException) to run before calling prisma.chat.create, then use the
validated selectedRepos when creating/continuing the chat and only call
captureEvent('wa_chat_thread_created', ...) and start the agent after validation
succeeds.
ℹ️ Review info
Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0dafb4b6-f261-4a59-b143-4d49afa158d8
📒 Files selected for processing (1)
packages/web/src/features/mcp/askCodebase.ts
Summary
/api/mcproute implementing the MCP Streamable HTTP transport with SSE supportgetAuthenticatedUserso MCP clients can authenticate with Sourcebot API keys viaAuthorization: Bearer <token>ask_codebase,search_code,read_file,list_repos,list_commits,list_tree,list_language_modelsutils.server.tsto fix'use server'module boundary violationswithAuthV2tests for the Bearer token authentication flow🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Tests
Chores