Skip to content

feat(mcp): add Basic Auth support for custom MCP server connectors#3187

Open
jericopingul wants to merge 3 commits into
PostHog:mainfrom
jericopingul:jp/dataforseo-mcp-basic-auth
Open

feat(mcp): add Basic Auth support for custom MCP server connectors#3187
jericopingul wants to merge 3 commits into
PostHog:mainfrom
jericopingul:jp/dataforseo-mcp-basic-auth

Conversation

@jericopingul

Copy link
Copy Markdown

Summary

  • Adds a "basic" auth type (username/password) to the custom MCP server install form, install flow, and mobile's custom-install screen, alongside the existing OAuth/API Key options.
  • Some third-party MCP servers (e.g. DataForSEO's remote MCP server) require standard HTTP Basic Auth (Authorization: Basic base64(username:password)), which the existing api_key type can't produce (it always sends Bearer <key>).
  • Client-side only: the outbound Authorization header is built server-side by the PostHog backend's MCP Store proxy. This PR hand-patches the generated API client types ahead of a real pnpm typed-openapi regen, and is inert until the backend recognizes "basic".

Depends on

Changes

  • packages/api-client: widen McpAuthType/InstallCustomAuthTypeEnum to include "basic"; add username/password to the install request shape.
  • packages/core/src/mcp-servers: thread username/password through customServerForm.ts and installFlow.ts, with new test coverage.
  • packages/ui/src/features/mcp-server-manager and mcp-servers: add the "Basic Auth" option with Username/Password fields to the Add Custom Server form, wired through both places it's consumed.
  • apps/mobile: mirror the same auth type and fields in the native "Add Custom Server" screen.

Test plan

  • pnpm --filter @posthog/core test — 196 files / 1982 tests passing
  • pnpm --filter @posthog/core typecheck
  • pnpm --filter @posthog/ui typecheck
  • pnpm --filter @posthog/api-client typecheck
  • pnpm --filter mobile test — 52 files / 394 tests passing
  • pnpm biome check on all touched files
  • End-to-end verification against DataForSEO once feat(mcp-store): add basic auth support to custom install flow posthog#68672 is deployed

🤖 Generated with Claude Code

Custom MCP servers that require HTTP Basic Auth (e.g. DataForSEO's remote
MCP server) had no way to connect — the only non-OAuth option was
"API Key", which sends `Authorization: Bearer <key>` and 401s against
Basic-auth-only upstreams. Adds a `"basic"` auth type (username/password)
across the install form, install flow, and mobile's custom-install screen.

Client-side only: the actual `Authorization: Basic ...` header is built
server-side by the PostHog backend proxy, which needs a matching change
(tracked in PostHog/posthog#68653) before this is functional end-to-end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Jul 6, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "Merge branch 'main' into jp/dataforseo-m..." | Re-trigger Greptile

Comment on lines +69 to +71
username:
authType === "basic" && username.trim() ? username.trim() : undefined,
password: authType === "basic" && password ? password : undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Credential fields sent independently, inconsistent with web behavior

Mobile sends username and password independently — a user who fills in a username but leaves the password blank will submit a request with auth_type: "basic" and username but no password. The web path (buildCustomServerRequest) requires both to be non-empty before including either, so it would silently drop both fields for the same partial input. Neither path validates that both are present before allowing submit, so in both cases the user gets a confusing backend error rather than clear client-side feedback. Consider adding a validation check (mirroring the name/url guards above) that requires both username and password to be non-empty when authType === "basic" before calling installCustomWithOAuth.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed — mobile submitted username/password independently, and web's buildCustomServerRequest silently dropped both on a partial pair, so either path could install a non-functional basic-auth server with only an opaque backend 401 later. Fixed by making canSubmitCustomServer() reject a lone username or password for authType === "basic" (wired into the web form's disabled-submit state), and adding the equivalent guard before installCustomWithOAuth on mobile.

Comment on lines +94 to +116
it("includes username/password only for basic auth when both are present", () => {
const req = buildCustomServerRequest(
values({ authType: "basic", username: " user ", password: "pass" }),
);
expect(req.username).toBe("user");
expect(req.password).toBe("pass");

expect(
buildCustomServerRequest(
values({ authType: "oauth", username: "user", password: "pass" }),
).username,
).toBeUndefined();
expect(
buildCustomServerRequest(
values({ authType: "basic", username: "", password: "pass" }),
).username,
).toBeUndefined();
expect(
buildCustomServerRequest(
values({ authType: "basic", username: "user", password: "" }),
).password,
).toBeUndefined();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Multiple scenarios packed into a single test — prefer parameterised tests

This it() block tests four independent scenarios (both present, wrong auth type, empty username, empty password) as sequential assertions. Per project convention, these should use it.each (or equivalent) so that each scenario is reported and fails independently. The installFlow.test.ts basic-auth test is a single case and is fine, but this block is the one that tests multiple distinct inputs.

Context Used: Do not attempt to comment on incorrect alphabetica... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — split into an it.each covering the exclude cases (wrong auth type, empty username, empty password) plus one plain it for the happy path, per the project's parameterised-test convention.

Web and mobile both let a user submit auth_type=basic with only one of
username/password filled in — web silently dropped both fields, mobile
sent the partial pair — so the install would succeed but the connection
would fail later with an opaque backend 401 instead of clear client-side
feedback. canSubmitCustomServer() now treats a lone username or password
as incomplete; api_key/oauth are unaffected since they have no pairing
requirement.

Also splits the buildCustomServerRequest basic-auth test into it.each
per the project's parameterised-test convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@charlesvien charlesvien requested a review from cvolzer3 July 6, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant