File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -134,6 +134,23 @@ describe('MCP OAuth start route', () => {
134134 expect ( body . error ) . toBe ( 'OAuth authorization already in progress for this server' )
135135 expect ( mockMcpAuth ) . not . toHaveBeenCalled ( )
136136 } )
137+
138+ it ( 'does not leak non-OAuth internal error details to the client' , async ( ) => {
139+ mcpOauthMockFns . mockGetOrCreateOauthRow . mockRejectedValueOnce (
140+ new Error ( 'connect ECONNREFUSED 10.0.0.5:5432 (internal-db-host)' )
141+ )
142+ const request = new NextRequest (
143+ 'http://localhost:3000/api/mcp/oauth/start?workspaceId=workspace-1&serverId=server-1'
144+ )
145+
146+ const response = await GET ( request )
147+ const body = await response . json ( )
148+
149+ expect ( response . status ) . toBe ( 500 )
150+ expect ( body . error ) . toBe ( 'Failed to start OAuth flow' )
151+ expect ( body . error ) . not . toContain ( 'ECONNREFUSED' )
152+ expect ( body . error ) . not . toContain ( 'internal-db-host' )
153+ } )
137154} )
138155
139156describe ( 'surfaceOauthError' , ( ) => {
Original file line number Diff line number Diff line change @@ -150,7 +150,11 @@ export const GET = withRouteHandler(
150150 }
151151 } catch ( error ) {
152152 logger . error ( 'Error starting MCP OAuth flow:' , error )
153- return createMcpErrorResponse ( toError ( error ) , surfaceOauthError ( error ) , 500 )
153+ // Only surface OAuth-flow errors verbatim; everything else (DB, decryption,
154+ // network) gets a generic message to avoid leaking internal details.
155+ const userMessage =
156+ error instanceof OAuthError ? surfaceOauthError ( error ) : 'Failed to start OAuth flow'
157+ return createMcpErrorResponse ( toError ( error ) , userMessage , 500 )
154158 }
155159 } )
156160)
Original file line number Diff line number Diff line change @@ -673,13 +673,17 @@ class McpService {
673673 async clearCache ( workspaceId ?: string ) : Promise < void > {
674674 try {
675675 if ( workspaceId ) {
676- const servers = await this . getWorkspaceServers ( workspaceId )
676+ // Enumerate without enabled/deletedAt filters so disabled or soft-deleted
677+ // servers still get their cache keys dropped (hard-deleted rows are gone
678+ // from the table and their keys expire naturally via TTL).
679+ const rows = await db
680+ . select ( { id : mcpServers . id } )
681+ . from ( mcpServers )
682+ . where ( eq ( mcpServers . workspaceId , workspaceId ) )
677683 await Promise . allSettled (
678- servers . map ( ( s ) => this . cacheAdapter . delete ( serverCacheKey ( workspaceId , s . id ) ) )
679- )
680- logger . debug (
681- `Cleared MCP tool cache for workspace ${ workspaceId } (${ servers . length } servers)`
684+ rows . map ( ( r ) => this . cacheAdapter . delete ( serverCacheKey ( workspaceId , r . id ) ) )
682685 )
686+ logger . debug ( `Cleared MCP tool cache for workspace ${ workspaceId } (${ rows . length } servers)` )
683687 } else {
684688 await this . cacheAdapter . clear ( )
685689 logger . debug ( 'Cleared all MCP tool cache' )
You can’t perform that action at this time.
0 commit comments