1+ import { createHash } from 'node:crypto'
12import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js'
23import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
34import { ErrorCode , McpError } from '@modelcontextprotocol/sdk/types.js'
@@ -90,6 +91,7 @@ type DiscoveryOutcome =
9091 kind : 'fetched'
9192 tools : McpTool [ ]
9293 resolvedConfig : McpServerConfig
94+ sourceConfig : McpServerConfig
9395 resolvedIP : string | null
9496 mutation : CacheMutation | null
9597 }
@@ -110,6 +112,42 @@ interface CacheMutation {
110112 id : number
111113}
112114
115+ interface DiscoveryRevisionInput {
116+ transport : string | null
117+ url : string | null
118+ authType : string | null
119+ oauthClientId : string | null
120+ oauthClientSecret : string | null
121+ headers : unknown
122+ timeout : number | null
123+ retries : number | null
124+ enabled : boolean
125+ }
126+
127+ function getDiscoveryRevision ( config : DiscoveryRevisionInput ) : string {
128+ const headers =
129+ config . headers && typeof config . headers === 'object' && ! Array . isArray ( config . headers )
130+ ? Object . entries ( config . headers as Record < string , unknown > ) . sort ( ( [ left ] , [ right ] ) =>
131+ left . localeCompare ( right )
132+ )
133+ : [ ]
134+ return createHash ( 'sha256' )
135+ . update (
136+ JSON . stringify ( {
137+ transport : config . transport ,
138+ url : config . url ,
139+ authType : config . authType ,
140+ oauthClientId : config . oauthClientId ,
141+ oauthClientSecret : config . oauthClientSecret ,
142+ headers,
143+ timeout : config . timeout ,
144+ retries : config . retries ,
145+ enabled : config . enabled ,
146+ } )
147+ )
148+ . digest ( 'hex' )
149+ }
150+
113151type ServerStatusUpdate =
114152 | {
115153 outcome : 'connected'
@@ -275,6 +313,7 @@ class McpService {
275313 enabled : server . enabled ,
276314 createdAt : server . createdAt . toISOString ( ) ,
277315 updatedAt : server . updatedAt . toISOString ( ) ,
316+ discoveryRevision : getDiscoveryRevision ( server ) ,
278317 }
279318 }
280319
@@ -305,6 +344,7 @@ class McpService {
305344 enabled : server . enabled ,
306345 createdAt : server . createdAt . toISOString ( ) ,
307346 updatedAt : server . updatedAt . toISOString ( ) ,
347+ discoveryRevision : getDiscoveryRevision ( server ) ,
308348 } ) )
309349 . filter ( ( config ) => isMcpDomainAllowed ( config . url ) )
310350 }
@@ -712,7 +752,21 @@ class McpService {
712752 null ,
713753 [ ]
714754 )
715- return ownership === 'superseded' ? 'superseded' : 'unavailable'
755+ if ( ownership === 'superseded' ) return 'superseded'
756+
757+ const currentConfig = await this . getServerConfig ( config . id , workspaceId )
758+ if (
759+ ! currentConfig ||
760+ ! config . discoveryRevision ||
761+ currentConfig . discoveryRevision !== config . discoveryRevision
762+ ) {
763+ await this . applyServerCacheMutation ( workspaceId , config . id , mutation , null , [
764+ serverCacheKey ( workspaceId , config . id ) ,
765+ failureCacheKey ( workspaceId , config . id ) ,
766+ ] )
767+ return 'superseded'
768+ }
769+ return 'unavailable'
716770 }
717771
718772 private async publishFailedDiscovery (
@@ -827,7 +881,14 @@ class McpService {
827881 logger . debug (
828882 `[${ requestId } ] Discovered ${ tools . length } tools from server ${ config . name } `
829883 )
830- return { kind : 'fetched' , tools, resolvedConfig, resolvedIP, mutation }
884+ return {
885+ kind : 'fetched' ,
886+ tools,
887+ resolvedConfig,
888+ sourceConfig : config ,
889+ resolvedIP,
890+ mutation,
891+ }
831892 } finally {
832893 await client . disconnect ( )
833894 }
@@ -861,7 +922,7 @@ class McpService {
861922 if ( outcome . kind === 'fetched' ) {
862923 const publication = await this . publishSuccessfulDiscovery (
863924 workspaceId ,
864- outcome . resolvedConfig ,
925+ outcome . sourceConfig ,
865926 outcome . mutation ,
866927 outcome . tools
867928 )
@@ -1038,7 +1099,7 @@ class McpService {
10381099 logger . info ( `[${ requestId } ] Discovered ${ tools . length } tools from server ${ config . name } ` )
10391100 const publication = await this . publishSuccessfulDiscovery (
10401101 workspaceId ,
1041- resolvedConfig ,
1102+ config ,
10421103 mutation ,
10431104 tools
10441105 )
0 commit comments