Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .specs/mcp-gateway-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ when they appear in all capitals.
| `GET /.well-known/oauth-authorization-server/oauth/authorize` | App canonical route, Worker discovery alias | Metadata alias for clients that discover from the authorization route. The Worker alias redirects to the app canonical route. |
| `GET /.well-known/oauth-authorization-server/mcp-connect/...` | Worker discovery alias | Path-aware compatibility alias for clients that start discovery from one scoped connect URL; redirects to app canonical metadata. |
| `POST /api/mcp-gateway/oauth/register` | App | Dynamic client registration. |
| `POST /api/mcp-gateway/oauth/register/{scope}/{owner_id}/{config_id}/{route_key}` | App | Resource-specific registration after route eligibility discovery. |
| `POST /api/mcp-gateway/oauth/register/resource/{scope}/{owner_id}/{config_id}/{route_key}` | App | Resource-specific registration after route eligibility discovery. |
| `GET|PUT|DELETE /api/mcp-gateway/oauth/register/{client_id}` | App | Registration management authorized by registration token. |
| `GET /api/mcp-gateway/oauth/authorize` | App | Generic authorization-code flow; requires `resource`. |
| `GET /api/mcp-gateway/oauth/authorize/{scope}/{owner_id}/{config_id}/{route_key}` | App | Route-specific authorization-code flow. |
Expand Down Expand Up @@ -371,4 +371,3 @@ when they appear in all capitals.
- Group/team assignment.
- External `/v0.1/servers` registry projection.
- A Worker-side provider token-exchange API.
- Dashboard UI or feature-flagged management pages.
79 changes: 79 additions & 0 deletions apps/web/src/app/(app)/cloud/mcp-gateway/ConnectionStatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { cn } from '@/lib/utils';

type ConnectionStatusInput = {
enabled: boolean;
authMode: string;
activeGrantCount: number;
assignmentCount: number;
};

type StatusTone = 'positive' | 'attention' | 'neutral';

type ConnectionStatus = {
label: string;
description: string;
tone: StatusTone;
};

export function getConnectionStatus(connection: ConnectionStatusInput): ConnectionStatus {
if (!connection.enabled) {
return { label: 'Disabled', description: 'Requests are blocked', tone: 'neutral' };
}
if (connection.authMode === 'none' || connection.authMode === 'static_headers') {
return { label: 'Ready', description: 'No provider sign-in required', tone: 'positive' };
}
if (connection.activeGrantCount > 0) {
if (connection.assignmentCount > connection.activeGrantCount) {
return {
label: 'Partially signed in',
description: `${connection.activeGrantCount} of ${connection.assignmentCount} assigned users have active grants`,
tone: 'attention',
};
}
return {
label: 'Signed in',
description:
connection.assignmentCount > 0
? `${connection.activeGrantCount} assigned users have active grants`
: 'A user has an active grant',
tone: 'positive',
};
}
return { label: 'Needs sign-in', description: 'No active provider grant yet', tone: 'attention' };
}

const toneDot: Record<StatusTone, string> = {
positive: 'bg-green-400',
attention: 'bg-yellow-400',
neutral: 'bg-muted-foreground',
};

const toneClassName: Record<StatusTone, string> = {
positive: 'bg-green-500/10 text-green-400 ring-green-500/20',
attention: 'bg-yellow-500/10 text-yellow-300 ring-yellow-500/20',
neutral: 'bg-secondary text-muted-foreground ring-border',
};

export function ConnectionStatusBadge({
connection,
className,
}: {
connection: ConnectionStatusInput;
className?: string;
}) {
const status = getConnectionStatus(connection);
return (
<span
className={cn(
'inline-flex h-6 items-center gap-1.5 rounded-md px-2 text-xs font-medium ring-1',
toneClassName[status.tone],
className
)}
aria-label={`${status.label}: ${status.description}`}
title={status.description}
>
<span aria-hidden className={cn('size-1.5 rounded-full', toneDot[status.tone])} />
{status.label}
</span>
);
}
Loading
Loading