Migrate MCP SDK: mark3labs/mcp-go -> official go-sdk via new abstraction layer - #8
Migrate MCP SDK: mark3labs/mcp-go -> official go-sdk via new abstraction layer#8LackOfMorals wants to merge 7 commits into
Conversation
…w abstraction layer The MCP spec and its SDKs move fast, including breaking changes, and this codebase had zero abstraction around mark3labs/mcp-go: SDK types were spelled out directly in the server, every tool spec/handler, and every test tier. Introduces internal/mcpsdk as the single package allowed to import an MCP SDK, and migrates the server, all tools, and all tests onto it, backed by the official github.com/modelcontextprotocol/go-sdk instead of the community mark3labs/mcp-go (which is now fully removed from go.mod). Also fixes 4 pre-existing golangci-lint findings (ElementId -> ElementID naming in internal/database/json_tagged_values.go) and adds a CI workflow that actually runs the test suites, since none previously did. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…test TestServerLifecycle polled a fixed 4-second wall-clock window for s.Start() to fail, without ever waiting on the goroutine that ran it. The "invalid host"/"invalid database" cases only resolve once the driver's own connection/DNS-resolution attempt times out, which can take noticeably longer over a CI runner's network than on a developer machine, so the test would see a false nil at the 4s mark. Switched to a channel-based wait so the error cases resolve as soon as Start() actually returns, with a generous 30s safety net for the cases that require a real network timeout to elapse. This is unrelated to the MCP SDK migration itself (this file has no MCP SDK dependency) — it's a pre-existing flake that was never exercised in CI until the previous commit added a workflow that actually runs the test suites. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Root-caused the CI failure: |
…ycle test The previous fix's 30s external wait was racing the neo4j driver's own default MaxTransactionRetryTime (also 30s): a bad host is classified as a transient/retryable ConnectivityError, so ExecuteReadQuery quietly retried for its full default budget before ever returning, and lost the race against this test's timeout in CI (confirmed in the PR's CI run: "expected Start() to fail within 30s, but it did not return"). Configure the test's driver with a 3s MaxTransactionRetryTime / 2s SocketConnectTimeout instead of padding the external wait further. Verified locally against an unreachable host: this surfaces the failure in ~6s (driver retry + backoff overhead), well inside the now-reduced 15s external wait. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n test The read-only-mode e2e assertions (3 tools / 4 tools) predate the give-feedback tool, added 2026-08-19 in 5ff9d24, but this e2e test's counts were never updated to include it. give-feedback is read-only, so it belongs in both counts: 4 for read-only mode (get-schema, read-cypher, list-gds-procedures, give-feedback) and 5 with all tools enabled (adding write-cypher) — matching the counts internal/server/tool_register_test.go already asserts. Unrelated to the MCP SDK migration; a pre-existing staleness that had never been exercised in CI until this PR's new test workflow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
CI is now green (run https://github.com/neo4j-labs/neo4j-mcp-canary/actions/runs/34537845656) — unit, integration, and e2e all pass. Two more pre-existing issues surfaced and fixed along the way, both unrelated to the MCP SDK migration itself (neither file has an MCP SDK dependency), just never exercised in CI until this PR added a workflow that runs the suites:
|
… releases CheckMinimumVersion rejected every bare classic-versioned release string (e.g. "5.26", "5.27") outright, regardless of how new it was, unless it carried Aura's "-aura" suffix. The correct requirement is a plain floor of 5.26 on the classic major.minor version — the suffix has no bearing on it. 5.26 is Neo4j's last classic-versioned release and an LTS. Also fixes a regression the merge introduced along the way: the classic pattern's unconstrained \d+.\d+ would otherwise also match calendar-shaped strings like "2026.06", so the calendar check now runs first (it's unambiguous — an exact 4-digit year) to avoid misclassifying a too-old calendar release as an accepted classic one. With the gate fixed, add real (non-mocked) integration coverage for internal/queryapi.Service, which previously only had coverage against httptest.Server fakes: bump the shared test container to neo4j:5.26-community and expose its Query API port alongside Bolt, then exercise the version gate, read-cypher, and write-cypher against it for real in test/integration/queryapi_test.go. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ompatible test image CheckMinimumVersion's classicVersionPattern only matched major.minor (e.g. "5.26"), but real self-managed servers report a full patch version (e.g. "5.26.30") — the exact string the CI-run container's discovery endpoint returned. Added the same optional patch group calendarVersionPattern already has. Separately: running the new Query API integration tests against a real neo4j:5.26-community container in CI revealed every request failing with 406 Not Acceptable. Root cause: github.com/neo4j-contrib/query-go-sdk v0.6.0 (our pinned, and currently newest published, dependency) hardcodes its typed-JSON media type to "v1.1", which per Neo4j's Query API changelog was introduced in the 2025.11 calendar release — a self-managed classic release like 5.26 predates that and doesn't recognize it, independent of the version gate. The gate's 5.26 floor is still correct (this is a client wire-protocol limitation, not a statement about what Neo4j itself supports), but the integration test container needs to be wire-compatible with our current SDK pin, so it's switched to neo4j:2026.07-community, which does support v1.1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…r to 5.26 Correction to the previous fix: the "-aura" suffix requirement wasn't the bug — dropping it was. A bare classic version (self-managed, no "-aura" suffix) must still be rejected regardless of how new it is, since self-managed classic servers predate the typed-JSON media type version query-go-sdk depends on (confirmed by the 406s the previous fix's own CI run produced against a real neo4j:5.26-community container). What was genuinely wrong was the floor number: classic Aura versions require at least 5.26-aura, not 5.27-aura as previously coded. Restores classicAuraVersionPattern's suffix requirement, lowers minClassicAuraMajor/Minor to 5/26, and updates version_test.go, README.md, and the changelog fragment to match. No changes needed to the integration test container (already calendar-versioned for wire-compatibility reasons unrelated to this suffix rule) or to test/integration/queryapi_test.go. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
mark3labs/mcp-go— SDK types were spelled out directly in the server, every tool spec/handler, and every test tier.internal/mcpsdkas the single package allowed to import an MCP SDK (plusinternal/mcpsdk/mcpsdktest, a matching client wrapper used only by this repo's own test suites), and migrates the server, all five tools, and every test tier onto it.mark3labs/mcp-gois fully removed fromgo.mod; the server and tools are now backed by the officialgithub.com/modelcontextprotocol/go-sdk, which was already an indirect dependency of this repo.golangci-lintfindings (ElementId->ElementIDnaming ininternal/database/json_tagged_values.go) found while linting the branch..github/workflows/test.ymlto actually run the unit/integration/e2e suites in CI — previously no workflow ran tests at all..changes/unreleased/mcp-sdk-abstraction.yaml) documenting the change for the next release's changelog.Two real behavioral gaps were found and fixed as part of the migration (not just mechanical renames):
mcpsdk.Server.ServeStdionow treats a closed/EOF stdin as a clean shutdown, matching prior behavior.CallToolRequest.BindArgumentscould panic on a nilParams— hardened with a nil check.Test plan
go build ./...,go vet ./...,go test ./...all passgolangci-lint runreports 0 issuesgo build -tags=e2e ./test/e2e/...andgo build -tags=integration ./test/integration/...compile cleanlytask test:int/task test:e2eagainst a real Neo4j (needs Docker for testcontainers) — not run in this environment, recommended before mergetask buildproduces a working binary (--versionreports correctly)🤖 Generated with Claude Code