Skip to content

feat(EVO-1739): stateless POST /custom-mcp-servers/test-connection (test-before-save) - #21

Merged
gomessguii merged 3 commits into
developfrom
feat/EVO-1739-mcp-test-connection-endpoint
Jul 24, 2026
Merged

feat(EVO-1739): stateless POST /custom-mcp-servers/test-connection (test-before-save)#21
gomessguii merged 3 commits into
developfrom
feat/EVO-1739-mcp-test-connection-endpoint

Conversation

@pastoriniMatheus

@pastoriniMatheus pastoriniMatheus commented Jul 23, 2026

Copy link
Copy Markdown

EVO-1739 (backend) — POST /custom-mcp-servers/test-connection (test-before-save)

Metade backend do botão Testar do wizard MCP. A metade frontend (botão no wizard) vem junto.

Por quê

O wizard precisa testar url/headers antes de salvar, mas só havia GET /:id/test (exige servidor persistido). Adiciona um endpoint stateless.

Mudança

  • service: TestConnection(ctx, url, headers) — wrapper público fino sobre o testConnection privado (que já delega o handshake MCP real ao processor /custom-mcp-servers/test-connection, EVO-2139, com propagação de X-Evo-Tenant-Id). Retorna o mesmo TestResult (success, status_code, tools_count, message/error).
  • handler: TestConnection bind {url(obrigatório), headers} (ValidationErrorResponse em corpo inválido); rota POST /custom-mcp-servers/test-connection, gate ai_custom_mcp_servers:read.
  • Segurança: não abre superfície nova além do que um servidor salvo-e-testado já permite; mesmo gate de permissão.

Testes

TestTestConnection_PublicWrapper_DelegatesAndReturnsResult (reusa o processor httptest scriptável) — delega pro path certo e devolve o resultado. go build/vet/test ./pkg/custom_mcp_server/... verde.

O arquivo já é CRLF no develop (pré-existente); mantido fora do gofmt pra evitar diff só de fim-de-linha.

Summary by Sourcery

Introduce a stateless endpoint to test custom MCP server connections before saving configuration.

New Features:

  • Add POST /custom-mcp-servers/test-connection endpoint to validate unsaved MCP server URL and headers using existing test infrastructure.

Enhancements:

  • Expose a TestConnection method on the custom MCP server service that delegates to the existing MCP handshake processor and returns the standard TestResult.

Tests:

  • Add a service test ensuring the public TestConnection wrapper delegates to the processor endpoint and returns the test result unchanged.

…est-before-save)

The MCP wizard needs to test a server's url/headers BEFORE it is saved, but the only
test endpoint was GET /:id/test (requires a persisted server). Add a stateless
POST /custom-mcp-servers/test-connection that accepts {url, headers} and returns the
same TestResult (success, status_code, tools_count, message/error).

- service: expose TestConnection(ctx, url, headers) — a thin public wrapper over the
  existing private testConnection (which already delegates the real MCP handshake to
  the processor's /custom-mcp-servers/test-connection, EVO-2139, with tenant-header
  propagation). No new outbound surface beyond what a saved-then-tested server already
  allows; gated by ai_custom_mcp_servers:read.
- handler: TestConnection binds {url(required), headers}, ValidationErrorResponse on
  bad body; route POST /custom-mcp-servers/test-connection.
- test: TestConnection public wrapper delegates to the processor test-connection path
  and returns the result unchanged. go build/vet/test ./pkg/custom_mcp_server/... green.

Backend half of the EVO-1739 wizard "Test" button; the frontend wires it to the wizard.
Note: the file is pre-existing CRLF on develop, so it stays out of gofmt to avoid a
line-ending-only diff.
@sourcery-ai

sourcery-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a stateless POST /custom-mcp-servers/test-connection endpoint and a corresponding service wrapper so the MCP wizard can test arbitrary URL/headers before persisting a server, reusing the existing processor-based MCP handshake and exposing the same TestResult structure.

Sequence diagram for stateless POST /custom-mcp-servers/test-connection

sequenceDiagram
  actor WizardFrontend
  participant CustomMcpServerHandler
  participant CustomMcpServerService
  participant Processor

  WizardFrontend->>CustomMcpServerHandler: POST /custom-mcp-servers/test-connection
  CustomMcpServerHandler->>CustomMcpServerHandler: ShouldBindJSON
  alt invalid body
    CustomMcpServerHandler-->>WizardFrontend: ValidationErrorResponse
  else valid body
    CustomMcpServerHandler->>CustomMcpServerService: TestConnection(ctx, url, headers)
    CustomMcpServerService->>Processor: testConnection(ctx, url, headers)
    Processor-->>CustomMcpServerService: TestResult
    CustomMcpServerService-->>CustomMcpServerHandler: TestResult
    CustomMcpServerHandler-->>WizardFrontend: SuccessResponse(test_result)
  end
Loading

File-Level Changes

Change Details Files
Expose a stateless test-before-save HTTP endpoint for custom MCP servers.
  • Extended CustomMcpServerHandler interface with a TestConnection method.
  • Registered POST /custom-mcp-servers/test-connection under the ai_custom_mcp_servers:read permission gate.
  • Implemented handler TestConnection to validate JSON body (required url, optional headers), call the service, and return a standardized success or validation/error response.
pkg/custom_mcp_server/handler/custom_mcp_server_handler.go
Introduce a public service-level TestConnection wrapper that reuses the existing MCP handshake processor.
  • Extended CustomMcpServerService interface with TestConnection(ctx, url, headers) returning TestResult.
  • Implemented customMcpServerService.TestConnection as a thin wrapper delegating to the existing private testConnection function.
pkg/custom_mcp_server/service/custom_mcp_server_service.go
Add unit coverage to ensure the new TestConnection wrapper delegates correctly and surfaces the processor result unchanged.
  • Added TestTestConnection_PublicWrapper_DelegatesAndReturnsResult using the capture test server to assert the processor path and returned TestResult fields.
  • Kept existing TestTestConnection_PropagatesTenantHeader_WhenBound test intact to ensure tenant header behavior remains unchanged.
pkg/custom_mcp_server/service/custom_mcp_server_service_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="pkg/custom_mcp_server/handler/custom_mcp_server_handler.go" line_range="319-328" />
<code_context>
 }
+
+// TestConnection tests an UNSAVED MCP server's url/headers (test-before-save). EVO-1739.
+func (h *customMcpServerHandler) TestConnection(c *gin.Context) {
+	var req struct {
+		URL     string            `json:"url" binding:"required"`
+		Headers map[string]string `json:"headers"`
+	}
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.ValidationErrorResponse(c, err)
+		return
+	}
+
+	testResult, err := h.customMcpServerService.TestConnection(c.Request.Context(), req.URL, req.Headers)
+	if err != nil {
+		code, message, httpCode := errors.HandleError(err)
</code_context>
<issue_to_address>
**🚨 issue (security):** Consider adding stricter validation/constraints on the URL to avoid SSRF-style misuse.

Because this endpoint forwards a client-provided URL directly to `TestConnection`, it may allow SSRF (e.g., internal services, metadata endpoints), depending on that implementation. Please consider tightening input by restricting schemes (e.g., HTTPS only), adding host/IP allow/deny lists, and/or reusing any existing URL validation used for persisted MCP servers so this “test” path isn’t more permissive than the saved one.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +319 to +328
func (h *customMcpServerHandler) TestConnection(c *gin.Context) {
var req struct {
URL string `json:"url" binding:"required"`
Headers map[string]string `json:"headers"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.ValidationErrorResponse(c, err)
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 issue (security): Consider adding stricter validation/constraints on the URL to avoid SSRF-style misuse.

Because this endpoint forwards a client-provided URL directly to TestConnection, it may allow SSRF (e.g., internal services, metadata endpoints), depending on that implementation. Please consider tightening input by restricting schemes (e.g., HTTPS only), adding host/IP allow/deny lists, and/or reusing any existing URL validation used for persisted MCP servers so this “test” path isn’t more permissive than the saved one.

The new endpoint was gated on `ai_custom_mcp_servers:read`. That is not the same surface
a saved-and-tested server already exposes: GET /:id/test can only reach a url somebody
with `create` persisted, whereas this one drives an arbitrary outbound request from the
processor with caller-supplied url and headers, and hands back the status code, latency
and error. Under `read` that gives every read-only user a working SSRF probe against the
internal network. `create` is the permission that already means "point the processor at a
url of my choosing", so the gate now matches the capability.

The url is also validated before anything goes out: absolute http/https with a host,
which rejects file://, gopher:// and hostless urls. Deliberately NOT a private-IP
blocklist -- a self-hosted Evolution routinely runs its MCP servers on the same private
network, so that would break the common case while barely inconveniencing an attacker who
already holds `create`.

Tests: the handler package had none at all; adds 5 covering the binding (missing url,
non-string header values), the pass-through of a service validation error, the forwarded
payload and the response shape the frontend reads, plus 7 service cases for the url rules.
Also unorphans the EVO-2139 tenant-header comment, which the new test was inserted between.
…g part

The comments added in 6edfae6 argued the case instead of stating it. Keeps the two
non-obvious decisions -- why the gate is `create` and why there is no private-IP
blocklist -- and drops the rest. No behaviour change.
@gomessguii
gomessguii merged commit b01f9aa into develop Jul 24, 2026
5 checks passed
@gomessguii
gomessguii deleted the feat/EVO-1739-mcp-test-connection-endpoint branch July 24, 2026 22:53
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