Skip to content

feat(integrations): prefer OAuth for PostHog MCP#410

Draft
neubig wants to merge 1 commit into
mainfrom
agent/posthog-mcp-oauth
Draft

feat(integrations): prefer OAuth for PostHog MCP#410
neubig wants to merge 1 commit into
mainfrom
agent/posthog-mcp-oauth

Conversation

@neubig

@neubig neubig commented Jul 13, 2026

Copy link
Copy Markdown
Member
  • A human has tested these changes.

Why

PostHog is needed for OSS KPI monitoring from OpenHands. The catalog already had PostHog MCP through a personal API key fallback, but PostHog's current docs recommend OAuth for the hosted MCP server and reserve personal API keys for clients that cannot use OAuth.

Official sources checked:

Summary

  • Add a preferred PostHog OAuth MCP connection using https://oauth.posthog.com authorization, token, and registration endpoints.
  • Default the OAuth MCP transport to https://mcp.posthog.com/mcp?readonly=true with editable URL for approved write workflows or project/org pinning.
  • Keep the bearer-token personal API key fallback and save it under POSTHOG_PERSONAL_API_KEY by default.
  • Add a focused regression test for the PostHog MCP catalog configuration.

Issue Number

Closes #409

How to Test

Commands run:

npm run build:integrations
uv sync --group test
uv run pytest -q tests/test_catalogs.py tests/test_catalog_schema.py tests/test_integration_catalog_in_sync.py
uv run pytest -q
uv run python scripts/sync_extensions.py --check

Results:

  • Focused catalog/schema/in-sync tests: 107 passed, 30 warnings.
  • Full test suite: 461 passed, 35 warnings.
  • Sync check passed with the existing non-blocking coverage warning: not in any marketplace: ./plugins/issue-duplicate-checker.

Live evidence

Catalog discovery through the JS package:

node --input-type=module -e "import { getIntegrationCatalogEntry, listIntegrationCatalog } from './integrations/index.js'; const e = getIntegrationCatalogEntry('posthog'); console.log(JSON.stringify({id:e.id, optionIds:e.connectionOptions.map(o=>o.id), firstAuth:e.connectionOptions[0].auth.strategy, firstUrl:e.connectionOptions[0].transport.url, oauthFiltered:listIntegrationCatalog({mcp:true, oauth:true}).some(x=>x.id==='posthog')}, null, 2));"

Sanitized observation: PostHog resolves with options ["oauth", "api-key"], first auth oauth2, first URL https://mcp.posthog.com/mcp?readonly=true, and appears in listIntegrationCatalog({mcp:true, oauth:true}).

Catalog discovery through the Python package:

uv run python - <<'PY'
import json
import openhands_extensions
entry = openhands_extensions.get_integration_catalog_entry('posthog')
print(json.dumps({
    'id': entry['id'],
    'optionIds': [option['id'] for option in entry['connectionOptions']],
    'firstAuth': entry['connectionOptions'][0]['auth']['strategy'],
    'firstUrl': entry['connectionOptions'][0]['transport']['url'],
    'oauthFiltered': any(item['id'] == 'posthog' for item in openhands_extensions.list_integration_catalog(mcp=True, oauth=True)),
}, indent=2))
PY

Sanitized observation: same result as JS. The command emitted only the repo's existing deprecation warnings for the legacy raw catalog accessors.

Remote MCP unauthenticated discovery against the exact catalog URL:

curl -sS -D /tmp/posthog-readonly-headers.txt -o /tmp/posthog-readonly-body.txt \
  -X POST 'https://mcp.posthog.com/mcp?readonly=true' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"openhands-extensions-live-check","version":"0.0.0"}}}'

Sanitized observation: PostHog returned HTTP/2 401 with WWW-Authenticate: Bearer resource_metadata="https://mcp.posthog.com/.well-known/oauth-protected-resource/mcp", proving the hosted MCP endpoint is reachable and advertises OAuth protected-resource discovery.

OAuth metadata discovery:

python - <<'PY'
import json, urllib.request
for name, url in {
    'protected_resource': 'https://mcp.posthog.com/.well-known/oauth-protected-resource/mcp',
    'authorization_server': 'https://oauth.posthog.com/.well-known/oauth-authorization-server',
}.items():
    with urllib.request.urlopen(url, timeout=30) as response:
        data = json.load(response)
    print(name, data.get('resource') or data.get('issuer'), data.get('authorization_servers') or data.get('authorization_endpoint'), data.get('token_endpoint'), data.get('registration_endpoint'))
PY

Sanitized observation: protected resource is https://mcp.posthog.com/mcp, auth server is https://oauth.posthog.com, bearer method is header, and OAuth metadata exposes https://oauth.posthog.com/oauth/authorize/, https://oauth.posthog.com/oauth/token/, and https://oauth.posthog.com/oauth/register/. The advertised scopes include query:read and dashboard:read.

Credentialed MCP initialize attempt:

python - <<'PY'
# Sends Authorization: Bearer $POSTHOG_API_KEY without printing the value.
# Performs JSON-RPC initialize against https://mcp.posthog.com/mcp?readonly=true.
PY

Sanitized observation: the available POSTHOG_API_KEY reached PostHog but was rejected with 401 Invalid API key. Remaining credential-dependent verification is to repeat initialize/tools-list with a valid PostHog personal API key created with the MCP Server preset, or complete OAuth login through an MCP client.

Video/Screenshots

Not applicable - catalog/configuration change only.

Notes

This PR does not create, modify, or encode any KPI dashboards or PQL definitions. The OAuth default is read-only for safe inspection; write access should be explicitly approved before a later workflow removes readonly=true or uses a write-scoped key.

@github-actions github-actions Bot added the type: feat A new feature label Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add preferred PostHog MCP OAuth connection

2 participants