Skip to content

feat: make Hub MCP self-hosting production-ready#66

Merged
xernobyl merged 7 commits into
mainfrom
chore/mcp_server_production_ready
May 5, 2026
Merged

feat: make Hub MCP self-hosting production-ready#66
xernobyl merged 7 commits into
mainfrom
chore/mcp_server_production_ready

Conversation

@xernobyl

@xernobyl xernobyl commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes https://linear.app/formbricks/issue/ENG-759/hub-mcp-server-production-ready

This makes the Hub-side self-hosted MCP path production-ready enough for generated SDK/MCP clients to discover and target a real deployed Hub instance:

  • Adds unauthenticated runtime OpenAPI endpoints at /openapi.yaml and /openapi.json.
  • Loads the OpenAPI document once at startup instead of reparsing it on every request.
  • Adds PUBLIC_BASE_URL so self-hosted operators can advertise the correct public Hub URL behind ingress, TLS termination, reverse proxies, and path prefixes.
  • Surfaces PUBLIC_BASE_URL in .env.example, the Helm chart values, and Helm chart notes.
  • Copies openapi.yaml into the runtime Docker image so the public spec endpoints work in production containers.
  • Updates the OpenAPI server metadata and package guidance for self-hosted SDK/MCP base URL overrides.
  • Adds an opt-in MCP smoke test using the official MCP Go SDK to exercise the published MCP package against a configured Hub deployment by creating, listing, and deleting a feedback record.
  • Expands tests for config validation, OpenAPI serving, HTTP route protection, and MCP smoke-test helpers.

Remaining ENG-759 completion items outside this Hub PR:

  • Publish the generated @formbricks/hub-mcp package changes.
  • Publish equivalent MCP usage docs on hub.formbricks.com.
  • Run the real-client verification matrix and share the demo recording in #engineering-chat.
  • Resolve the current Stainless hub-typescript preview build/lint regression before release.

How should this be tested?

  • make build
  • make tests
  • make lint
  • make lint-openapi
  • Optional live MCP smoke test against a configured Hub deployment:
HUB_API_KEY=your-hub-api-key \
FORMBRICKS_HUB_BASE_URL=https://hub.example.com \
HUB_MCP_PACKAGE=@formbricks/hub-mcp@latest \
make mcp-smoke

For PUBLIC_BASE_URL, start Hub with a production-style value and verify both spec formats advertise it:

PUBLIC_BASE_URL=https://hub.example.com go run ./cmd/api
curl http://localhost:8080/openapi.yaml
curl http://localhost:8080/openapi.json

Expected result: both responses include https://hub.example.com as the current Hub server URL and /v1/* routes remain API-key protected.

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Repository Guidelines
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand bits
  • Ran make build
  • Ran make tests (integration tests in tests/)
  • Ran make fmt and make lint; no new warnings
  • Removed debug prints / temporary logging
  • Merged the latest changes from main onto my branch with git pull origin main
  • If database schema changed: added migration in migrations/ with goose annotations and ran make migrate-validate

Appreciated

  • If API changed: added or updated OpenAPI spec and ran contract tests (make tests or API contract workflow)
  • If API behavior changed: added request/response examples or Swagger UI screenshots to this PR
  • Updated docs in docs/ if changes were necessary
  • Ran make tests-coverage for meaningful logic changes

@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown

✱ Stainless preview builds

This PR will update the hub SDKs with the following commit message.

feat: make Hub MCP self-hosting production-ready
hub-openapi studio · code

Your SDK built successfully.
generate ✅

⚠️ hub-typescript studio · code

There was a regression in your SDK.
generate ✅build ❗lint ❗test ✅


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-05-05 10:46:08 UTC

@xernobyl xernobyl marked this pull request as ready for review April 29, 2026 17:47
@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown

Walkthrough

This PR adds OpenAPI specification serving capabilities, MCP smoke test infrastructure, and configuration for self-hosted deployments. The OpenAPI document is now served at /openapi.yaml and /openapi.json endpoints without authentication. A new PUBLIC_BASE_URL environment variable allows configuration of the base URL in OpenAPI responses for deployments behind reverse proxies, ingress controllers, or TLS terminators. MCP smoke test support is added with Make targets, dependency updates, and test infrastructure. Helm chart documentation and application configuration are updated accordingly.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: making Hub MCP self-hosting production-ready by adding environment configuration, OpenAPI endpoints, and deployment documentation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all key aspects of the changes with detailed testing instructions and a properly completed checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/api/app_test.go`:
- Around line 189-204: Add a new test mirroring
TestNewHTTPServerServesOpenAPIWithoutAuth but targeting "/openapi.yaml" and
ensuring the server behavior when PUBLIC_BASE_URL is unset: unset or clear the
PUBLIC_BASE_URL env var before calling newTestHTTPServer(), create the request
with httptest.NewRequestWithContext for "/openapi.yaml", call
server.Handler.ServeHTTP and assert recorder.Code == http.StatusOK, then assert
the response body contains the expected default public base URL string (use the
same substring checked in the JSON test, e.g. "https://hub.example.com/base" or
the package constant that defines the default base) so the regression for the
YAML endpoint is covered.

In `@cmd/api/app.go`:
- Around line 330-333: If handlers.NewOpenAPIHandler(...) fails in NewApp,
ensure you clean up previously-initialized resources before returning: call the
message manager and river client cleanup methods (e.g., messageManager.Close or
messageManager.Shutdown and riverClient.Close) and gracefully shutdown the
telemetry providers (tracerProvider and meterProvider) using their
Shutdown/ForceFlush APIs with a timeout context, then return the formatted
error; add this cleanup branch immediately before the current "return nil,
fmt.Errorf(...)" so resources initialized earlier in NewApp are not leaked.

In `@internal/api/handlers/openapi_handler.go`:
- Around line 216-227: requestBaseURL currently injects the raw r.Host into the
OpenAPI servers URL which allows host-header injection; update requestBaseURL to
first prefer an explicit PUBLIC_BASE_URL env/config and use that for the
returned base URL, and if PUBLIC_BASE_URL is unset do NOT blindly trust
r.Host—either return a safe localDevelopmentBaseURL only in development or
validate r.Host against an explicit allowlist (implement a helper like
isHostAllowed(host string) used by requestBaseURL) and reject/ignore unallowed
hosts; reference the function requestBaseURL, the variable
localDevelopmentBaseURL and the PUBLIC_BASE_URL configuration when making this
change.

In `@internal/config/config_test.go`:
- Around line 15-21: The tests in internal/config/config_test.go leak the parent
process's PUBLIC_BASE_URL when setPublicBaseURL is false; update the subtest
setup so that before calling Load() you capture the original
os.Getenv("PUBLIC_BASE_URL"), then if setPublicBaseURL is false explicitly unset
the env var (os.Unsetenv) and after the subtest restore the original value (or
clear if it was empty); apply the same change to the other table cases noted
(around the block referenced by lines 95-97) so all subtests deterministically
control PUBLIC_BASE_URL when exercising Load().

In `@Makefile`:
- Around line 96-127: The check-coverage-staged Makefile target is too large and
should be extracted into a small wrapper that calls a dedicated script to avoid
checkmake max-body-length warnings; move the 31-line shell block into a new
executable script (e.g., scripts/check_coverage_staged.sh) that accepts or reads
STAGED_PACKAGES and PRE_COMMIT_COVERAGE_THRESHOLD, performs the same steps
(compute STAGED_GO_FILES, build STAGED_GO_FILE_LIST, run go test to produce
coverage.staged.out, calculate COV with awk against MODULE, and exit non-zero on
failures), and replace the large target body with a one-line Make recipe that
invokes the script (preserving the target name check-coverage-staged and all
original environment variable usage and exit codes).

In `@openapi.yaml`:
- Around line 8-9: The comment points out that the README in openapi.yaml
mentions the wrong environment variable name; replace references to
FORMBRICKS_HUB_BASE_URL with the actual env var used by the runtime/server
config (PUBLIC_BASE_URL) so documentation matches the running configuration and
avoids confusing self-hosted users—update any occurrences of
FORMBRICKS_HUB_BASE_URL in openapi.yaml to PUBLIC_BASE_URL and ensure the
surrounding sentence clarifies that the published JS/TS packages use
PUBLIC_BASE_URL for overriding the Hub base URL.

In `@tests/mcp_smoke_test.go`:
- Around line 375-403: The created feedback record can be left behind if the
intermediate list call fails; wrap the listing and deletion in a try/finally so
deletion is always attempted: call client.feedbackRecords.create(...) as before
to get created (ensure created and created.id exist), then do the list call
inside a try block and in the finally block call await
client.feedbackRecords.delete(created.id) (with a safe existence check and
optional error swallow/log) before returning the result from the async function
run(client).
- Around line 352-365: The test helper failMCPTestf logs raw subprocess stderr
which can leak secrets (e.g. HUB_API_KEY passed into child env); update
failMCPTestf to redact known secret values before calling t.Log by scanning
stderr.lines (from tailWriter) and replacing occurrences of the HUB_API_KEY
value (and any other sensitive env values) with a fixed mask like "[REDACTED]";
ensure you read the secret value from the same source used to set the child env
and perform replacements on each line before logging so no raw credentials from
stderr.lines are emitted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f13ecac4-5b04-48b2-829a-ad22351f3234

📥 Commits

Reviewing files that changed from the base of the PR and between b22da19 and 56d4820.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (15)
  • .env.example
  • Dockerfile
  • Makefile
  • README.md
  • charts/hub/templates/NOTES.txt
  • charts/hub/values.yaml
  • cmd/api/app.go
  • cmd/api/app_test.go
  • go.mod
  • internal/api/handlers/openapi_handler.go
  • internal/api/handlers/openapi_handler_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • openapi.yaml
  • tests/mcp_smoke_test.go

Comment thread cmd/api/app_test.go
Comment thread cmd/api/app.go
Comment thread internal/api/handlers/openapi_handler.go Outdated
Comment thread internal/config/config_test.go
Comment thread Makefile Outdated
Comment thread openapi.yaml Outdated
Comment thread tests/mcp_smoke_test.go
Comment thread tests/mcp_smoke_test.go
@xernobyl xernobyl enabled auto-merge April 30, 2026 11:38
@xernobyl xernobyl requested a review from BhagyaAmarasinghe May 5, 2026 08:37
Comment thread tests/mcp_smoke_test.go Outdated
@xernobyl xernobyl disabled auto-merge May 5, 2026 10:41
@BhagyaAmarasinghe BhagyaAmarasinghe self-requested a review May 5, 2026 10:42
@xernobyl xernobyl added this pull request to the merge queue May 5, 2026
Merged via the queue into main with commit 3d3f819 May 5, 2026
9 of 10 checks passed
@xernobyl xernobyl deleted the chore/mcp_server_production_ready branch May 5, 2026 10:44
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.

2 participants