diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 837ed16b..7b2a601d 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -209,7 +209,7 @@ export const oauthApi = { clients: { list: () => apiClient.get('/oauth/clients').then(r => r.data), - create: (data: { name: string; redirect_uris: string[]; allowed_scopes: string[] }) => + create: (data: { name: string; redirect_uris?: string[]; allowed_scopes: string[]; allows_client_credentials?: boolean }) => apiClient.post('/oauth/clients', data).then(r => r.data), rotate: (id: string) => apiClient.post(`/oauth/clients/${id}/rotate`).then(r => r.data), delete: (id: string) => apiClient.delete(`/oauth/clients/${id}`).then(r => r.data), diff --git a/client/src/components/Settings/IntegrationsTab.tsx b/client/src/components/Settings/IntegrationsTab.tsx index 430da0f6..6daf674f 100644 --- a/client/src/components/Settings/IntegrationsTab.tsx +++ b/client/src/components/Settings/IntegrationsTab.tsx @@ -69,6 +69,7 @@ interface OAuthClient { client_id: string redirect_uris: string[] allowed_scopes: string[] + allows_client_credentials: boolean created_at: string client_secret?: string // only present on create } @@ -117,6 +118,7 @@ export default function IntegrationsTab(): React.ReactElement { const [oauthRotating, setOauthRotating] = useState(false) // oauthScopesOpen is managed internally by ScopeGroupPicker const [oauthScopesExpanded, setOauthScopesExpanded] = useState>({}) + const [oauthIsMachine, setOauthIsMachine] = useState(false) // MCP sub-tab state const [activeMcpTab, setActiveMcpTab] = useState<'oauth' | 'apitokens'>('oauth') @@ -214,16 +216,23 @@ export default function IntegrationsTab(): React.ReactElement { }, [mcpEnabled]) const handleCreateOAuthClient = async () => { - if (!oauthNewName.trim() || !oauthNewUris.trim()) return + if (!oauthNewName.trim()) return + if (!oauthIsMachine && !oauthNewUris.trim()) return setOauthCreating(true) try { - const uris = oauthNewUris.split('\n').map(u => u.trim()).filter(Boolean) - const d = await oauthApi.clients.create({ name: oauthNewName.trim(), redirect_uris: uris, allowed_scopes: oauthNewScopes }) + const uris = oauthIsMachine ? [] : oauthNewUris.split('\n').map(u => u.trim()).filter(Boolean) + const d = await oauthApi.clients.create({ + name: oauthNewName.trim(), + redirect_uris: uris, + allowed_scopes: oauthNewScopes, + ...(oauthIsMachine ? { allows_client_credentials: true } : {}), + }) setOauthCreatedClient(d.client) setOauthClients(prev => [...prev, { ...d.client, client_secret: undefined }]) setOauthNewName('') setOauthNewUris('') setOauthNewScopes([]) + setOauthIsMachine(false) } catch { toast.error(t('settings.oauth.toast.createError')) } finally { @@ -342,7 +351,7 @@ export default function IntegrationsTab(): React.ReactElement {

{t('settings.oauth.clientsHint')}

- @@ -360,7 +369,15 @@ export default function IntegrationsTab(): React.ReactElement {
-

{client.name}

+
+

{client.name}

+ {client.allows_client_credentials && ( + + {t('settings.oauth.badge.machine')} + + )} +

{t('settings.oauth.clientId')}: {client.client_id} {t('settings.mcp.tokenCreatedAt')} {new Date(client.created_at).toLocaleDateString(locale)} @@ -616,15 +633,26 @@ export default function IntegrationsTab(): React.ReactElement { autoFocus />

-
- -