diff --git a/.changeset/clear-toolset-user-session-issuer.md b/.changeset/clear-toolset-user-session-issuer.md new file mode 100644 index 0000000000..331b78c2e1 --- /dev/null +++ b/.changeset/clear-toolset-user-session-issuer.md @@ -0,0 +1,5 @@ +--- +"server": minor +--- + +Add `toolsets.clearUserSessionIssuer` endpoint that unlinks any user_session_issuer attached to a toolset. diff --git a/.changeset/remove-user-session-issuer-button.md b/.changeset/remove-user-session-issuer-button.md new file mode 100644 index 0000000000..75e7ed4aa0 --- /dev/null +++ b/.changeset/remove-user-session-issuer-button.md @@ -0,0 +1,5 @@ +--- +"dashboard": patch +--- + +Add admin-only "Remove user session issuer" button on the MCP authentication tab that unlinks the toolset's user_session_issuer via `toolsets.clearUserSessionIssuer`. diff --git a/.speakeasy/out.openapi.yaml b/.speakeasy/out.openapi.yaml index 9f6a20fda0..ca99bf8182 100644 --- a/.speakeasy/out.openapi.yaml +++ b/.speakeasy/out.openapi.yaml @@ -23037,6 +23037,115 @@ paths: x-speakeasy-name-override: checkMCPSlugAvailability x-speakeasy-react-hook: name: CheckMCPSlugAvailability + /rpc/toolsets.clearUserSessionIssuer: + post: + description: Unlink the user_session_issuer from a toolset. No-op if the toolset has no user_session_issuer linked. + operationId: clearToolsetUserSessionIssuer + parameters: + - allowEmptyValue: true + description: The slug of the toolset to unlink + in: query + name: slug + required: true + schema: + description: A short url-friendly label that uniquely identifies a resource. + maxLength: 40 + pattern: ^[a-z0-9_-]{1,128}$ + type: string + - allowEmptyValue: true + description: Session header + in: header + name: Gram-Session + schema: + description: Session header + type: string + - allowEmptyValue: true + description: API Key header + in: header + name: Gram-Key + schema: + description: API Key header + type: string + - allowEmptyValue: true + description: project header + in: header + name: Gram-Project + schema: + description: project header + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Toolset' + description: OK response. + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'bad_request: request is invalid' + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'unauthorized: unauthorized access' + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'forbidden: permission denied' + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'not_found: resource not found' + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'conflict: resource already exists' + "415": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'unsupported_media: unsupported media type' + "422": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'invalid: request contains one or more invalidation fields' + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'unexpected: an unexpected error occurred' + "502": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: 'gateway_error: an unexpected error occurred' + security: + - project_slug_header_Gram-Project: [] + session_header_Gram-Session: [] + - apikey_header_Gram-Key: [] + project_slug_header_Gram-Project: [] + - {} + summary: clearUserSessionIssuer toolsets + tags: + - toolsets + x-speakeasy-name-override: clearUserSessionIssuer + x-speakeasy-react-hook: + name: ClearToolsetUserSessionIssuer /rpc/toolsets.clone: post: description: Clone an existing toolset with a new name diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index ff601ca9f8..7c40e90e90 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -2,8 +2,8 @@ speakeasyVersion: 1.761.5 sources: Gram-Internal: sourceNamespace: gram-api-description - sourceRevisionDigest: sha256:4f5d0ec1263cc5417617a7cee7786490592d78f7f02371310ef59fe4a224f01f - sourceBlobDigest: sha256:16a89de83ab7c177db0d5f4bf86f6f3fbcad0a7b69c026c8bb9dc71377230dd2 + sourceRevisionDigest: sha256:bfd22e8d0ed4339094ead9f6495eb2f7fdeb0517a7e4b083c828ded8ff22e6bf + sourceBlobDigest: sha256:60b1aa90d8bcb74cd2e5774774f484821daeb278ed1b0d58c25c2182715b1c06 tags: - latest - 0.0.1 @@ -11,10 +11,10 @@ targets: gram-internal: source: Gram-Internal sourceNamespace: gram-api-description - sourceRevisionDigest: sha256:4f5d0ec1263cc5417617a7cee7786490592d78f7f02371310ef59fe4a224f01f - sourceBlobDigest: sha256:16a89de83ab7c177db0d5f4bf86f6f3fbcad0a7b69c026c8bb9dc71377230dd2 + sourceRevisionDigest: sha256:bfd22e8d0ed4339094ead9f6495eb2f7fdeb0517a7e4b083c828ded8ff22e6bf + sourceBlobDigest: sha256:60b1aa90d8bcb74cd2e5774774f484821daeb278ed1b0d58c25c2182715b1c06 codeSamplesNamespace: gram-api-description-typescript-code-samples - codeSamplesRevisionDigest: sha256:901f3d0135976214725cabf726813f14f2148dc9cf0938525f6216c163ced2bc + codeSamplesRevisionDigest: sha256:c6f49aecc7afa6086cc9a66bfe070e06208e7ae737183c6b9de68f18d178057f workflow: workflowVersion: 1.0.0 speakeasyVersion: pinned diff --git a/cli/internal/api/toolsets.go b/cli/internal/api/toolsets.go index 71675dc4a5..cab070e029 100644 --- a/cli/internal/api/toolsets.go +++ b/cli/internal/api/toolsets.go @@ -43,6 +43,7 @@ func NewToolsetsClient(options *ToolsetsClientOptions) *ToolsetsClient { h.AddOAuthProxyServer(), h.UpdateOAuthProxyServer(), h.SetUserSessionIssuer(), + h.ClearUserSessionIssuer(), ) return &ToolsetsClient{client: client} diff --git a/client/dashboard/src/pages/mcp/MCPEnvironmentSettings.tsx b/client/dashboard/src/pages/mcp/MCPEnvironmentSettings.tsx index 0f3740f540..374eb73e74 100644 --- a/client/dashboard/src/pages/mcp/MCPEnvironmentSettings.tsx +++ b/client/dashboard/src/pages/mcp/MCPEnvironmentSettings.tsx @@ -7,7 +7,7 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; -import { useSession } from "@/contexts/Auth"; +import { useIsAdmin, useSession } from "@/contexts/Auth"; import { useTelemetry } from "@/contexts/Telemetry"; import { useMissingRequiredEnvVars } from "@/hooks/useMissingEnvironmentVariables"; import { ONBOARD_EXTERNAL_MCP_TO_USER_SESSIONS_FLAG } from "@/lib/externalMcpUserSessions"; @@ -18,6 +18,7 @@ import { invalidateAllGetMcpMetadata, invalidateAllListEnvironments, invalidateAllToolset, + useClearToolsetUserSessionIssuerMutation, useCreateEnvironmentMutation, useGetMcpMetadata, useListEnvironments, @@ -902,10 +903,23 @@ function OAuthSection({ toolset }: OAuthSectionProps) { ] = useState(false); const telemetry = useTelemetry(); + const isAdmin = useIsAdmin(); + const queryClient = useQueryClient(); const { data: environmentsData } = useListEnvironments(); const environments = environmentsData?.environments ?? []; + const clearUserSessionIssuerMutation = + useClearToolsetUserSessionIssuerMutation({ + onSuccess: () => { + invalidateAllToolset(queryClient); + toast.success("Removed user session issuer from toolset"); + }, + onError: (err) => { + toast.error(`Failed to remove user session issuer: ${err.message}`); + }, + }); + const loginSecured = !!toolset.userSessionIssuerSlug; const isOAuthConnected = !!( toolset?.oauthProxyServer || toolset?.externalOauthServer @@ -973,6 +987,21 @@ function OAuthSection({ toolset }: OAuthSectionProps) { Login Secured )} + {userSessionIssuerWired && isAdmin && ( + + )} {showWireUserSessionIssuer && (