stacklok / stacklok/toolhive-studio
Enterprise UI does not consume directives from /enterprise/api/v1beta/config
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 167
- Forks
- 24
- Avg merge
- 11h 32m
- Merged PRs (30d)
- 91
Description
Summary
The Enterprise Studio Desktop app polls GET /enterprise/api/v1beta/config (provided by the enterprise overlay of thv serve) and extracts only state, warning, and policy from the response — ignoring the individual policy directives in the payload (playground, non_registry_servers, registry, telemetry, …).
As a result, when an administrator pushes a directive like playground: { value: false, enforcement: enforced } via the enterprise config server, the directive is signed into envelopes, propagated correctly to the Desktop's thv serve subprocess, and visible in the API response — but the Desktop UI takes no action. The Playground tab remains visible.
The permission keys, the gate, and the React data flow are all wired — only the mapping from directive value to permission state is missing.
Repro
- Deploy an enterprise-manager / config-server with a
playgrounddirective:enterprise-manager: enterpriseConfig: playground: value: false enforcement: "enforced" - Sign in to ToolHive Studio Enterprise; let it fetch a fresh envelope.
- Inspect the live API response:
Output (truncated):DESKTOP_PORT=$(ps -ef | grep -v grep | grep "ToolHiveStudioEnterprise.*thv serve" | head -1 \ | grep -oE -- "--port=[0-9]+" | grep -oE "[0-9]+") curl -s http://127.0.0.1:$DESKTOP_PORT/enterprise/api/v1beta/config | jq{ "playground": { "value": false, "enforcement": "enforced" }, "state": "online" } - Observe the Desktop UI — Playground tab still visible in the left navigation.
Expected
When playground.value === false is present in the envelope, the Desktop should hide the Playground tab. Same for any other directive whose semantics map to a UI permission.
Where the gap is (from a decompile of the bundled app)
The permission keys are defined and consumed correctly:
xB = {
AUTO_UPDATE: "auto-update",
CUSTOM_MCP_SERVERS: "non_registry_servers",
HELP_MENU: "help-menu",
PLAYGROUND_MENU: "playground-menu",
SETTINGS_REGISTRY_TAB: "settings-registry-tab",
};
// Used to gate the Playground tab:
t(xB.PLAYGROUND_MENU) && <PlaygroundMenuItem />
// canShow returns true by default if the key is missing:
function canShow(key) { return permissions[key] ?? true; }
But the React Query loader that polls the enterprise config only extracts state/warning/policy — it discards every directive:
function FH() {
let { data: e } = useQuery({
queryKey: ["enterprise", "config"],
queryFn: async () => { let {data} = await NH(); return data ?? null; },
refetchOnWindowFocus: true,
refetchInterval: ...,
});
return { state: e?.state, warning: e?.warning, policy: e?.policy };
// ← directives like e?.playground are not extracted, never reach the permissions context
}
Suggested fix
Extend the loader to translate directives into the permissions shape:
return {
state: e?.state,
warning: e?.warning,
policy: e?.policy,
permissions: {
"playground-menu": e?.playground?.value !== false,
"non_registry_servers": e?.non_registry_servers?.value !== false,
// … any other directive → permission mappings
},
};
…and feed the permissions field into the existing wB context that backs canShow. The rest of the UI is already wired.
Impact
- Enterprise administrators pushing UI-restriction directives (e.g. hide Playground for dev teams that should only run pre-approved MCP servers) see no behavior change in the Desktop, despite the directive being correctly signed, transported, and stored. The admin assumes the policy is broken.
- Similar bug for
non_registry_servers:thv serveenforces it at the API level (refuses to start non-registry servers), but the UI doesn't proactively hide the "add custom server" affordance — users hit the error only after attempting, and there's no admin-message banner. - Related (separate issue): when the config server enters
offline_grace_period/offline_degraded, the Desktop DOES show a banner (good!) — but it does not display thedegraded_mode.messagevalue the admin configured invalues.yaml. Worth folding in.
Versions tested: ToolHive Studio Enterprise 0.1.30 (built e8abecc).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Locate the React Query config loader (FH), its NH request path, and the existing wB permissions context; trace how canShow consumes permission keys. Compare directive fields in the response with the defined UI permission keys, then verify that false directive values hide the corresponding controls, including the Playground and custom-server affordances.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100