Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
692654e
feat(mcp-gateway): implement the team MCP gateway UI behind the mcp-g…
cvolzer3 Jul 31, 2026
ac00720
fix(mcp-gateway): resolve the added server in the register mutation i…
cvolzer3 Jul 31, 2026
d01fef1
feat: add member list search and preview
cvolzer3 Jul 31, 2026
37b8575
refactor: remove agent creation from team view
cvolzer3 Jul 31, 2026
f6d79c7
Remove agent call status from team view
cvolzer3 Jul 31, 2026
6c05fb8
feat(mcp-gateway): refine server detail experience
cvolzer3 Jul 31, 2026
19b6bcf
Unify MCP server detail row backgrounds
cvolzer3 Jul 31, 2026
6c91f32
Hide empty shared MCP server section
cvolzer3 Jul 31, 2026
40fe731
fix(mcp-gateway): remove service account prefix from agent handles
cvolzer3 Jul 31, 2026
e895d1d
feat(mcp-gateway): let members manage agent access
cvolzer3 Jul 31, 2026
685e5d9
fix(mcp-gateway): prevent stale policy and access state
cvolzer3 Jul 31, 2026
e3978d0
feat(mcp-gateway): refine team server management
cvolzer3 Jul 31, 2026
a89d1ca
fix(mcp-gateway): parse paginated members response
cvolzer3 Jul 31, 2026
2a202da
fix(mcp-gateway): drop shared connections from the gateway client
cvolzer3 Jul 31, 2026
285a5f0
feat(mcp-gateway): compose the servers home from the sparse registry …
cvolzer3 Jul 31, 2026
525d62d
fix(scouts): keep the scout enable switch grey when off
cvolzer3 Jul 31, 2026
4bbb9d7
fix(mcp-gateway): drop token rotation from the agent detail page
cvolzer3 Jul 31, 2026
2fde68e
refactor(mcp-gateway): drop the unrendered policy-baseline and rules …
cvolzer3 Jul 31, 2026
d607d59
fix(mcp-gateway): discover a server's tools when someone connects
cvolzer3 Jul 31, 2026
dc59141
fix(mcp-gateway): don't badge deleted-actor audit rows as "human"
cvolzer3 Jul 31, 2026
923a313
docs: correct isFeatureEnabled return value when flags never load
cvolzer3 Jul 31, 2026
f463fa5
fix(mcp-gateway): reset GiveAccessDialog draft so policies don't leak…
cvolzer3 Jul 31, 2026
ced13c0
fix(mcp-gateway): stop the rail showing disabled connections as conne…
cvolzer3 Jul 31, 2026
53347be
fix(mcp-gateway): use the template icon_domain for materialized serve…
cvolzer3 Jul 31, 2026
afd9e17
fix(mcp-servers): never include rule-locked tools in bulk approval wr…
cvolzer3 Jul 31, 2026
d2ecfb8
fix(mcp-gateway): derive the member delete affordance from the sessio…
cvolzer3 Jul 31, 2026
0fd92e1
fix(mcp-gateway): show one bulk trio and drop Needs Approval for agen…
cvolzer3 Jul 31, 2026
c8b4a12
fix(mcp-gateway): honor the server's auth type when connecting instea…
cvolzer3 Jul 31, 2026
f11f3d3
fix(mcp-gateway): stamp the session user as grantor in the optimistic…
cvolzer3 Jul 31, 2026
c4ecb71
fix(mcp-gateway): give connected members a refresh-tools affordance
cvolzer3 Jul 31, 2026
81ad90f
fix(mcp-gateway): reset access draft with dialog key
cvolzer3 Jul 31, 2026
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
49 changes: 49 additions & 0 deletions docs/LOCAL-DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,59 @@ region you pick at login.

## Troubleshooting

### Feature flags never enabled (flag-gated UI missing)

If flag-gated surfaces (e.g. the MCP gateway behind `mcp-gateway`) never show up
even though the flag is enabled in your PostHog project, check
`VITE_POSTHOG_API_HOST` in `.env`: it must include the scheme
(`http://localhost:8010`, not `localhost:8010`). posthog-js concatenates the
host into request URLs verbatim, so a scheme-less value produces URLs like
`localhost:8010/flags/…` that the browser rejects as an invalid protocol —
every flag fetch fails silently and `isFeatureEnabled` returns `undefined` for
everything (flags never loaded). Prefer `node scripts/use-local-posthog.mjs`
over hand-editing; it writes the correct form.

To confirm what the running app sees, run in the renderer console (or via CDP):

```js
posthog.config.api_host; // must start with http:// or https://
posthog.isFeatureEnabled("mcp-gateway"); // undefined ⇒ flags never loaded
```

`.env` changes need a dev-server restart (`pnpm dev`) to take effect.

### "Invalid client_id" error during OAuth

The OAuth application in your local PostHog must have the client ID `DC5uRLVbGI02YQ82grxgnK6Qn12SXWpCqdPb60oZ`. Verify at http://localhost:8010/admin/posthog/oauthapplication/.

### "OAuth error: invalid_scope"

PostHog Code requests the wildcard scope `*` (see `OAUTH_SCOPES` in
`packages/shared/src/oauth.ts`). PostHog's OAuth server only grants `*` at
`/authorize` when the OAuth application's **scope ceiling is empty** — this is
the grandfathering path for the PostHog Code client. If the application has any
explicit `scopes` or `optional_scopes` configured, the wildcard is rejected with
`invalid_scope`.

Fix: clear the scope ceiling on your local OAuth application so it matches the
production app. Either edit it at
http://localhost:8010/admin/posthog/oauthapplication/ (empty the **Scopes** and
**Optional scopes** fields), or run in your PostHog repo:

```bash
python manage.py shell -c "
from posthog.models.oauth import OAuthApplication
app = OAuthApplication.objects.get(client_id='DC5uRLVbGI02YQ82grxgnK6Qn12SXWpCqdPb60oZ')
app.scopes = []
app.optional_scopes = []
app.save()
print('cleared scope ceiling for', app.client_id)
"
```

Then retry login. (Do not add `*` to the ceiling — an explicit ceiling never
grants the wildcard, even if `*` is listed.)

### "Redirect URI mismatch"

Make sure the OAuth application's redirect URIs include `http://localhost:8237/callback` and `http://localhost:8239/callback`. Check for trailing slashes.
Expand Down
208 changes: 208 additions & 0 deletions packages/api-client/src/mcp-gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Types for the team MCP gateway API (`/api/projects/{id}/mcp_gateway/*`).
// Hand-written mirrors of the Django serializers in
// products/mcp_store/backend/presentation/gateway_views.py — these endpoints
// ship behind the `mcp-gateway` flag and are not in the generated OpenAPI
// client yet.
import type { Schemas } from "./generated";
import type { McpApprovalState, McpAuthType, McpCategory } from "./types";

export type McpGatewayUser = Schemas.UserBasic;

export type McpGatewayScopeType = "team" | "member" | "agent";
export type McpServiceAccountStatus = "active" | "paused";
export type McpAuditDecision = "auto" | "approved" | "pending" | "blocked";
export type McpAuditQuickFilter = "all" | "agents" | "approvals" | "blocked";
export type McpPolicyDecidedBy =
| "rule"
| "scope"
| "team"
| "preset"
| "legacy"
| "default";

/** One member's connection to a gateway server. */
export interface McpGatewayConnection {
installation_id: string;
user: McpGatewayUser;
last_used_at: string | null;
pending_oauth: boolean;
needs_reauth: boolean;
}

/** The requesting user's own connection to a gateway server. */
export interface McpGatewayYourConnection {
installation_id: string;
/** Per-connection switch — false when self-disabled. */
is_enabled: boolean;
pending_oauth: boolean;
needs_reauth: boolean;
last_used_at: string | null;
}

/** One agent's access to a gateway server. */
export interface McpGatewayAgentAccess {
service_account_id: string;
name: string;
/** Agent identity handle, e.g. posthog-support. */
handle: string;
status: McpServiceAccountStatus;
last_active_at: string | null;
granted_by: McpGatewayUser | null;
}

/** A server registered in the team's gateway, with connection summary. */
export interface McpGatewayServer {
id: string;
name: string;
url: string;
description: string;
category: McpCategory;
is_team_enabled: boolean;
icon_key: string;
docs_url: string;
template_id: string | null;
/**
* Fixed authentication type for catalog templates. Null for custom
* servers, where each member chooses when connecting.
*/
template_auth_type: McpAuthType | null;
tool_count: number;
/** Members with a connection to this server. Admin-only; empty for members. */
connections: McpGatewayConnection[];
your_connection: McpGatewayYourConnection | null;
agents: McpGatewayAgentAccess[];
/** Ids of members whose access an admin has turned off. */
revoked_user_ids: number[];
is_revoked_for_you: boolean;
created_by: McpGatewayUser | null;
created_at: string;
updated_at: string;
}

export interface McpGatewayServerUpdate {
name?: string;
description?: string;
category?: McpCategory;
/** Master switch — off means members and agents can neither see nor call the server. */
is_team_enabled?: boolean;
}

/** Which policy scope a tools query or policy upsert targets. */
export interface McpGatewayPolicyScope {
scope_type?: McpGatewayScopeType;
/** Member scope target. Defaults to the requesting user. */
scope_user_id?: number;
/** Agent scope target. Required when scope_type is "agent". */
scope_service_account_id?: string;
}

export interface McpToolPolicyEntry {
tool_name: string;
policy_state: McpApprovalState;
}

/** One tool with its effective policy for the requested scope. */
export interface McpResolvedToolPolicy {
tool_name: string;
description: string;
input_schema: unknown;
policy_state: McpApprovalState;
/** What the team-level chain yields, ignoring the scope. Null when the team imposes nothing. */
team_state: McpApprovalState | null;
/** True when a rule or Blocked team ceiling leaves no editable state. */
locked: boolean;
decided_by: McpPolicyDecidedBy;
/** Matching org rule name, when decided_by is "rule". */
rule_name: string;
rule_description: string;
}

export interface McpServiceAccount {
id: string;
name: string;
description: string;
/** Stable identity handle the agent authenticates as, e.g. posthog-support. */
handle: string;
status: McpServiceAccountStatus;
/** Masked bearer token; the full token is only shown once. */
token_mask: string;
server_ids: string[];
last_active_at: string | null;
created_at: string;
updated_at: string;
}

export interface McpServiceAccountWithToken extends McpServiceAccount {
/** The full bearer token. Returned exactly once — on creation. */
token: string;
}

export interface McpAuditActorServiceAccount {
id: string;
name: string;
handle: string;
}

export interface McpAuditEvent {
id: string;
created_at: string;
server_name: string;
tool_name: string;
decision: McpAuditDecision;
actor_user: McpGatewayUser | null;
actor_service_account: McpAuditActorServiceAccount | null;
/** Denormalized actor label (email or handle) that survives deletion. */
actor_label: string;
}

export interface McpAuditCounts {
all: number;
agents: number;
approvals: number;
blocked: number;
}

export interface McpAuditPage {
count: number;
results: McpAuditEvent[];
}

export interface TeamMcpGatewayConfig {
allow_custom_servers: boolean;
/** Whether members may share MCP connections with agents and manage agent tool policies. */
allow_member_agent_access: boolean;
/**
* Whether catalog servers the team never touched (no gateway row) are
* enabled. Covers templates published after the admin last curated.
*/
default_servers_enabled: boolean;
/** Whether the requesting user can administer the gateway. */
is_admin: boolean;
}

export interface TeamMcpGatewayConfigUpdate {
allow_custom_servers?: boolean;
allow_member_agent_access?: boolean;
default_servers_enabled?: boolean;
}

/** One team member's gateway posture (admin overview). */
export interface McpGatewayMemberSummary {
user: McpGatewayUser;
is_org_admin: boolean;
/** Gateway servers the member has a personal connection to. */
connected_server_ids: string[];
/** Gateway servers an admin turned off for this member. */
revoked_server_ids: string[];
}

/**
* Gateway options accepted by install_custom / install_template. Credentials
* are always personal to the installer; agents reach them through grants.
*/
export interface McpGatewayInstallSharingOptions {
/** Whether the server starts enabled for the whole team. */
team_enabled?: boolean;
/** Service accounts to grant the server to at install time, when team settings allow it. */
agent_ids?: string[];
}
Loading
Loading