From 79f1ecd378b2061e117345e297e01d11a655e538 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Thu, 24 Sep 2026 20:54:10 +0200 Subject: [PATCH 1/2] Tenant Config Report: show self-hosted AI client registrations (#162) The registrations (name, callback URL, client id) live in their own table, not in TenantConfiguration, so the report only showed the limit. Fetch GET tenants/{tenantId}/mcp-client-registrations fail-soft alongside the other side views and render them in the Tenant Status section, plus the platform switch. The limit row is relabelled to say it is a limit. Claude-Session: https://claude.ai/code/session_01DbRs7NCppizQ7wWFjN7n3M Co-authored-by: Claude --- .../sections/SectionTenantConfigReport.tsx | 41 +++++++++++++++++-- .../sections/tenantConfigReportCatalog.ts | 2 +- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/Web/autopilot-monitor-web/app/admin/tenants/sections/SectionTenantConfigReport.tsx b/src/Web/autopilot-monitor-web/app/admin/tenants/sections/SectionTenantConfigReport.tsx index d41ad011..5f8eaf04 100644 --- a/src/Web/autopilot-monitor-web/app/admin/tenants/sections/SectionTenantConfigReport.tsx +++ b/src/Web/autopilot-monitor-web/app/admin/tenants/sections/SectionTenantConfigReport.tsx @@ -6,6 +6,7 @@ import { api } from '@/lib/api'; import { apiErrorText, fetchJson } from "@/lib/apiClient"; import type { AgentConfigResponse, + McpClientRegistrationListResponse, TenantConfiguration, TenantFeatureFlagsResponse, } from "@/utils/wire-types.generated"; @@ -205,6 +206,31 @@ function ChannelRows({ config }: { config: TenantConfiguration }) { ); } +// ── Self-hosted AI clients (separate table, not part of TenantConfiguration) ── + +function SelfHostedAiClientRows({ list }: { list: McpClientRegistrationListResponse }) { + return ( + <> + + {list.registrations.length === 0 && } + {list.registrations.map((r) => ( + + + + + + + + + +
+ + + ))} + + ); +} + // ── Main component ─────────────────────────────────────────────────────────── export function SectionTenantConfigReport() { @@ -215,6 +241,7 @@ export function SectionTenantConfigReport() { const [config, setConfig] = useState(null); const [flags, setFlags] = useState(null); const [effective, setEffective] = useState(null); + const [aiClients, setAiClients] = useState(null); const [loading, setLoading] = useState(false); const [loadingTenants, setLoadingTenants] = useState(true); const [error, setError] = useState(null); @@ -242,27 +269,31 @@ export function SectionTenantConfigReport() { fetchTenants(); }, [user?.isGlobalAdmin, getAccessToken]); - // Fetch the stored config plus the two backend-resolved views of it. The stored config is - // the report; feature-flags (effective entitlement, header) and the effective agent config - // (runtime column) are fail-soft — a notice replaces them instead of blocking the page. + // Fetch the stored config plus the two backend-resolved views of it and the self-hosted AI + // client registrations (own table). The stored config is the report; feature-flags (effective + // entitlement, header), the effective agent config (runtime column) and the registrations are + // fail-soft — a notice replaces them instead of blocking the page. const fetchConfig = useCallback(async () => { if (!selectedTenantId) return; try { setLoading(true); setError(null); setSideError(null); - const [configResult, flagsResult, effectiveResult] = await Promise.allSettled([ + const [configResult, flagsResult, effectiveResult, aiClientsResult] = await Promise.allSettled([ fetchJson(api.config.tenant(selectedTenantId), getAccessToken), fetchJson(api.config.featureFlags(selectedTenantId), getAccessToken), fetchJson(api.config.effectiveAgentConfig(selectedTenantId), getAccessToken), + fetchJson(api.tenants.mcpClientRegistrations(selectedTenantId), getAccessToken), ]); if (configResult.status === 'rejected') throw configResult.reason; setConfig(configResult.value); setFlags(flagsResult.status === 'fulfilled' ? flagsResult.value : null); setEffective(effectiveResult.status === 'fulfilled' ? effectiveResult.value : null); + setAiClients(aiClientsResult.status === 'fulfilled' ? aiClientsResult.value : null); const sideFailures = [ flagsResult.status === 'rejected' ? `feature flags: ${apiErrorText(flagsResult.reason)}` : null, effectiveResult.status === 'rejected' ? `effective agent config: ${apiErrorText(effectiveResult.reason)}` : null, + aiClientsResult.status === 'rejected' ? `self-hosted AI clients: ${apiErrorText(aiClientsResult.reason)}` : null, ].filter((s): s is string => s !== null); setSideError(sideFailures.length ? `Backend-resolved views unavailable — ${sideFailures.join('; ')}` : null); } catch (err) { @@ -270,6 +301,7 @@ export function SectionTenantConfigReport() { setConfig(null); setFlags(null); setEffective(null); + setAiClients(null); } finally { setLoading(false); } @@ -433,6 +465,7 @@ export function SectionTenantConfigReport() { /> ); })} + {section === 'Tenant Status' && aiClients && } ))} diff --git a/src/Web/autopilot-monitor-web/app/admin/tenants/sections/tenantConfigReportCatalog.ts b/src/Web/autopilot-monitor-web/app/admin/tenants/sections/tenantConfigReportCatalog.ts index 9c13524f..b99c22d0 100644 --- a/src/Web/autopilot-monitor-web/app/admin/tenants/sections/tenantConfigReportCatalog.ts +++ b/src/Web/autopilot-monitor-web/app/admin/tenants/sections/tenantConfigReportCatalog.ts @@ -98,7 +98,7 @@ export const TENANT_FIELDS: Record Date: Fri, 25 Sep 2026 05:43:05 +0200 Subject: [PATCH 2/2] fix(web): self-hosted AI clients are ones the organization hosts itself (#163) "Runs on its own servers" read as on-premises only and collided with the MCP server. The criterion is who hosts the client (on premises or in the cloud), matching the customer docs. Claude-Session: https://claude.ai/code/session_012P3YpD3CSsR8Sy1Ubg4pUw Co-authored-by: Claude --- .../app/settings/tenant/sections/SectionAiIntegration.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionAiIntegration.tsx b/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionAiIntegration.tsx index 50e9b304..ea896b53 100644 --- a/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionAiIntegration.tsx +++ b/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionAiIntegration.tsx @@ -169,7 +169,7 @@ function SelfHostedClients() { tone="skyIndigo" iconPath="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" title="Self-hosted AI clients" - subtitle="Only for an AI client your organization runs on its own servers. Claude, ChatGPT, VS Code and other hosted assistants need nothing here." + subtitle="Only for an AI client your organization hosts itself. Claude, ChatGPT, VS Code and other hosted assistants need nothing here." docsPath={DOCS_PATHS.selfHostedClients} />