fix: read the MCP URL policy when a URL is asked for, and drop the all-workspaces option

Two review findings on the token drawer:

The policy was read once per page load and cached for the browser session, so a
superadmin turning the setting on left every open tab handing out `?token=` URLs
the server now refuses. Both entry points now read it when the user actually asks
for an MCP URL: when MCP mode is entered, and when the connect drawer opens.

The workspace picker offered "All workspaces / Multi-workspace", but the gateway's
consent screen binds the token it issues to the one workspace picked there, so
OAuth has no multi-workspace grant to hand out. That entry is now token-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-09-16 10:38:24 +02:00
co-authored by Claude Opus 5
parent 0cd9306068
commit 7206f92f63
3 changed files with 20 additions and 32 deletions
@@ -6,7 +6,6 @@
import { Bot, ExternalLink, Terminal } from 'lucide-svelte'
import { shell } from 'svelte-highlight/languages'
import { mcpTokenUrlDisabled } from '$lib/mcpAuth'
import { onMount } from 'svelte'
type ConnectTab = 'cli' | 'mcp'
@@ -22,15 +21,12 @@ wmill workspace add ${workspaceId} ${workspaceId} ${origin}
wmill init
wmill sync pull`)
onMount(async () => {
tokenUrlDisabled = await mcpTokenUrlDisabled()
})
function noop() {}
export function openDrawer(tab: ConnectTab = 'cli') {
selectedTab = tab
openVersion += 1
void mcpTokenUrlDisabled().then((v) => (tokenUrlDisabled = v))
drawer?.openDrawer()
}
@@ -1,5 +1,5 @@
<script lang="ts">
import { onMount, untrack } from 'svelte'
import { untrack } from 'svelte'
import { userWorkspaces, workspaceStore, type UserWorkspace } from '$lib/stores'
import { Alert, Button } from '../common'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
@@ -75,6 +75,7 @@
function enterMcpMode() {
mcpCreationMode = true
void mcpTokenUrlDisabled().then((v) => (tokenUrlDisabled = v))
newTokenExpiration = undefined
newTokenWorkspace = defaultNewTokenWorkspace ?? $workspaceStore
newToken = undefined
@@ -157,12 +158,6 @@
)
const mcpBaseUrl = $derived(`${mcpUrl}?token=`)
onMount(async () => {
if (showMcpMode || mcpOnly || openWithMcpMode) {
tokenUrlDisabled = await mcpTokenUrlDisabled()
}
})
$effect(() => {
const requestedMcpMode = mcpOnly || openWithMcpMode
if (requestedMcpMode === lastRequestedMcpMode) {
@@ -225,16 +220,11 @@
{#if !lockWorkspace}
<div class="mb-4 max-w-md">
<span class="block mb-1 text-emphasis text-xs font-semibold">Workspace</span>
<!-- No all-workspaces entry: the gateway's consent screen binds the token it issues
to the one workspace picked there, so OAuth has no multi-workspace grant to offer. -->
<Select
bind:value={newTokenWorkspace}
items={[
{
label: 'All workspaces',
value: ALL_WORKSPACES,
subtitle: 'Multi-workspace'
},
...workspaces.map((w) => ({ label: w.name, value: w.id, subtitle: w.id }))
]}
items={workspaces.map((w) => ({ label: w.name, value: w.id, subtitle: w.id }))}
/>
</div>
{/if}
+14 -12
View File
@@ -1,20 +1,22 @@
import { SettingService } from '$lib/gen'
let cached: Promise<boolean> | undefined
/**
* Whether the instance refuses `?token=` on the MCP endpoints. When it does, an MCP URL is
* handed over bare and the client reaches it by completing the OAuth flow, so nothing in the
* UI should offer to mint a token for one.
*
* Deliberately uncached: callers read it at the moment an MCP URL is asked for, so a superadmin
* flipping the setting does not leave open tabs handing out URLs the server now refuses.
*/
export function mcpTokenUrlDisabled(): Promise<boolean> {
cached ??= SettingService.getGlobal({ key: 'mcp_disable_token_query_param' })
.then((v) => (v as boolean | null) ?? false)
.catch((err) => {
console.error('Failed to load the MCP token setting:', err)
// Retry on the next caller rather than pinning the fallback for the session.
cached = undefined
return false
})
return cached
export async function mcpTokenUrlDisabled(): Promise<boolean> {
try {
return (
((await SettingService.getGlobal({
key: 'mcp_disable_token_query_param'
})) as boolean | null) ?? false
)
} catch (err) {
console.error('Failed to load the MCP token setting:', err)
return false
}
}