@@ -21,6 +21,8 @@ import { clientFetch } from "@app/lib/egress/client";
2121import {
2222 useMCPServer ,
2323 useMCPServers ,
24+ useMCPServersUsage ,
25+ useMCPServerViewsFromSpaces ,
2426 useMutateMCPServersViewsForAdmin ,
2527} from "@app/lib/swr/mcp_servers" ;
2628import { useSpacesAsAdmin } from "@app/lib/swr/spaces" ;
@@ -58,11 +60,31 @@ export function MCPServerDetails({
5860 onClose,
5961 readOnly = false ,
6062} : MCPServerDetailsProps ) {
61- const { spaces } = useSpacesAsAdmin ( {
63+ const { spaces, isSpacesLoading , isSpacesError } = useSpacesAsAdmin ( {
6264 workspaceId : owner . sId ,
6365 disabled : ! isOpen || ! isAdmin ( owner ) ,
6466 } ) ;
6567
68+ const {
69+ serverViews : directlyAvailableMCPServerViews ,
70+ isLoading : isDirectAvailabilityLoading ,
71+ isError : isDirectAvailabilityError ,
72+ mutateServerViews : mutateDirectlyAvailableMCPServerViews ,
73+ } = useMCPServerViewsFromSpaces ( owner , spaces , {
74+ disabled : ! isOpen || readOnly ,
75+ } ) ;
76+ const isDirectAvailabilityResolved =
77+ ! isSpacesLoading &&
78+ ! isSpacesError &&
79+ ! isDirectAvailabilityLoading &&
80+ ! isDirectAvailabilityError ;
81+ const isRestrictedToSkills =
82+ isDirectAvailabilityResolved &&
83+ mcpServerView !== null &&
84+ ! directlyAvailableMCPServerViews . some (
85+ ( view ) => view . server . sId === mcpServerView . server . sId
86+ ) ;
87+
6688 const { server : mcpServerWithViews , mutateMCPServer } = useMCPServer ( {
6789 owner,
6890 serverId : mcpServerView ?. server . sId ?? "" ,
@@ -89,6 +111,10 @@ export function MCPServerDetails({
89111 owner,
90112 disabled : ! isOpen || readOnly ,
91113 } ) ;
114+ const { usage, mutate : mutateMCPServersUsage } = useMCPServersUsage ( {
115+ owner,
116+ disabled : ! isOpen || readOnly ,
117+ } ) ;
92118
93119 // Collect all effective view names from other servers (excluding the current one).
94120 const existingViewNames = useMemo (
@@ -105,19 +131,23 @@ export function MCPServerDetails({
105131
106132 const defaults = useMemo < MCPServerFormValues > ( ( ) => {
107133 if ( mcpServerView ) {
108- return getMCPServerFormDefaults (
109- mcpServerView ,
110- mcpServerWithViews ?? undefined ,
111- spaces
112- ) ;
134+ return {
135+ ...getMCPServerFormDefaults (
136+ mcpServerView ,
137+ mcpServerWithViews ?? undefined ,
138+ spaces
139+ ) ,
140+ isRestrictedToSkills,
141+ } ;
113142 }
114143 return {
115144 name : "" ,
116145 description : "" ,
146+ isRestrictedToSkills : false ,
117147 toolSettings : { } ,
118148 sharingSettings : { } ,
119149 } ;
120- } , [ mcpServerView , mcpServerWithViews , spaces ] ) ;
150+ } , [ isRestrictedToSkills , mcpServerView , mcpServerWithViews , spaces ] ) ;
121151
122152 const form = useForm < MCPServerFormValues > ( {
123153 values : defaults ,
@@ -136,6 +166,33 @@ export function MCPServerDetails({
136166 : undefined ,
137167 } ) ;
138168
169+ const confirmDirectAvailabilityChange = async (
170+ isAvailableToAgents : boolean
171+ ) : Promise < boolean > => {
172+ if ( isAvailableToAgents || ! mcpServerView ) {
173+ return true ;
174+ }
175+
176+ const affectedAgents = usage ?. [ mcpServerView . server . sId ] ?. agents ?? [ ] ;
177+ if ( affectedAgents . length === 0 ) {
178+ return true ;
179+ }
180+
181+ return confirm ( {
182+ title : "Remove this tool from agents?" ,
183+ message : (
184+ < div >
185+ This tool is currently used directly by { affectedAgents . length } { " " }
186+ { affectedAgents . length === 1 ? "agent" : "agents" } . Saving this change
187+ will remove it from those agents. Skills using this tool will not be
188+ affected.
189+ </ div >
190+ ) ,
191+ validateLabel : "Continue" ,
192+ validateVariant : "warning" ,
193+ } ) ;
194+ } ;
195+
139196 const applyToolChanges = async (
140197 toolChanges : Array < {
141198 toolName : string ;
@@ -217,20 +274,22 @@ export function MCPServerDetails({
217274
218275 const applyInfoChanges = async ( diff : {
219276 serverView ?: { name : string ; description : string } ;
277+ isRestrictedToSkills ?: boolean ;
220278 icon ?: string ;
221279 authSharedSecret ?: string ;
222280 authCustomHeaders ?: any ;
223281 authMeta ?: Record < string , string > | null ;
224282 } ) => {
225283 const hasServerViewChanges = diff . serverView !== undefined ;
284+ const hasSkillsOnlyChanges = diff . isRestrictedToSkills !== undefined ;
226285 const hasIconChanges = diff . icon !== undefined ;
227286 const hasSecretChanges = diff . authSharedSecret !== undefined ;
228287 const hasHeaderChanges = diff . authCustomHeaders !== undefined ;
229288 const hasMetaChanges = diff . authMeta !== undefined ;
230289 const hasRemoteChanges =
231290 hasIconChanges || hasSecretChanges || hasHeaderChanges || hasMetaChanges ;
232291
233- if ( ! hasServerViewChanges && ! hasRemoteChanges ) {
292+ if ( ! hasServerViewChanges && ! hasSkillsOnlyChanges && ! hasRemoteChanges ) {
234293 return ;
235294 }
236295
@@ -250,6 +309,23 @@ export function MCPServerDetails({
250309 }
251310 }
252311
312+ if ( hasSkillsOnlyChanges ) {
313+ const response = await clientFetch (
314+ `/api/w/${ owner . sId } /mcp/views/${ mcpServerView ?. sId } ` ,
315+ {
316+ method : "PATCH" ,
317+ headers : { "Content-Type" : "application/json" } ,
318+ body : JSON . stringify ( {
319+ isRestrictedToSkills : diff . isRestrictedToSkills ,
320+ } ) ,
321+ }
322+ ) ;
323+ if ( ! response . ok ) {
324+ const body = await response . json ( ) ;
325+ throw new Error ( body . error ?. message ?? "Failed to update server view" ) ;
326+ }
327+ }
328+
253329 // Patch remote server settings if needed. icon and meta use separate
254330 // requests because they are distinct discriminants in the API schema.
255331 // sharedSecret and customHeaders can be combined in one request.
@@ -357,6 +433,8 @@ export function MCPServerDetails({
357433 // Revalidate caches.
358434 await mutateMCPServersViewsForAdmin ( ) ;
359435 await mutateMCPServer ( ) ;
436+ await mutateMCPServersUsage ( ) ;
437+ await mutateDirectlyAvailableMCPServerViews ( ) ;
360438
361439 sendNotification ( {
362440 type : "success" ,
@@ -442,6 +520,8 @@ export function MCPServerDetails({
442520 spaces = { spaces }
443521 readOnly = { readOnly }
444522 sensitivityLabelsController = { sensitivityLabelsController }
523+ confirmDirectAvailabilityChange = { confirmDirectAvailabilityChange }
524+ isDirectAvailabilityResolved = { isDirectAvailabilityResolved }
445525 />
446526 </ FormProvider >
447527 ) ;
0 commit comments