From d3678260c26e054e4bb93f752597d47deada8322 Mon Sep 17 00:00:00 2001 From: amirejaz Date: Fri, 31 Jul 2026 16:22:53 +0100 Subject: [PATCH] Pin mcp-server-time e2e image by digest The proxy e2e suites fail on every PR with "timeout waiting for MCP server to be ready". Same root cause as #6159, a different image: tag 2026.7.10 of mcp-server-time is mutable, dockyard rebuilds it and resolves Python dependencies fresh, and the 2026-07-31 rebuild picked up the mcp Python SDK 2.0.0 released 2026-07-28. mcp-server-time imports McpError from mcp.shared.exceptions, which 2.x moved, so the container now exits on startup with "ImportError: cannot import name 'McpError'". Pin the last build that resolved a 1.x mcp. Verified by driving both builds over stdio: the pinned digest answers initialize, tag 2026.7.10 does not. Point the two affected suites at the pinned constant instead of the "time" registry entry, since the registry resolves to the moving tag and is not fixable from this repo. Registry-name resolution stays covered by the sibling cases that run "osv". stdio_proxy_over_streamable_http gains an explicit --transport stdio, which the registry entry previously supplied. Co-Authored-By: Claude Opus 5 (1M context) --- test/e2e/images/images.go | 27 +++++++++++++++++++ test/e2e/proxy_stdio_test.go | 5 ++-- ...xy_over_streamable_http_mcp_server_test.go | 4 ++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/test/e2e/images/images.go b/test/e2e/images/images.go index d1dd46e2ec..0f2e9a2f7e 100644 --- a/test/e2e/images/images.go +++ b/test/e2e/images/images.go @@ -117,6 +117,33 @@ const ( // Provides ~64 PagerDuty incident management tools (incidents, services, schedules, etc.). PagerDutyMCPServerImage = pagerdutyMCPServerImageURL + ":" + pagerdutyMCPServerImageTag + timeMCPServerImageURL = "ghcr.io/stacklok/dockyard/uvx/mcp-server-time" + timeMCPServerImageTag = "2026.7.10" + // timeMCPServerImageDigest pins the 2026-07-27 build of tag 2026.7.10. + // + // Same failure mode as idaProMCPServerImageDigest above, different symptom: + // the tag is mutable, dockyard rebuilds it and resolves Python dependencies + // fresh, and the 2026-07-31 rebuild picked up the mcp Python SDK 2.0.0 + // released 2026-07-28. mcp-server-time imports McpError from + // mcp.shared.exceptions, which 2.x moved, so the container now exits on + // startup with "ImportError: cannot import name 'McpError' from + // 'mcp.shared.exceptions'". The proxy suites then fail with + // "timeout waiting for MCP server to be ready". + // + // Pin the last build that resolved a 1.x mcp until upstream caps the + // constraint or adopts the 2.x API. Verified by driving both builds over + // stdio: the pinned digest answers initialize, tag 2026.7.10 does not. + timeMCPServerImageDigest = "sha256:5cca77dec3fefbacad35e3008ab1660f8725ef246213afb24231504fc73c999d" + // TimeMCPServerImage is the stdio backend used by the proxy e2e suites + // (proxy_stdio_test.go, stdio_proxy_over_streamable_http_mcp_server_test.go). + // Provides get_current_time / convert_time. + // + // These suites reference this constant rather than the "time" registry entry + // so a mutable upstream tag cannot break CI: the registry resolves "time" to + // the moving :2026.7.10 tag, which is currently broken. Registry-name + // resolution itself is still covered by the suites that run "osv". + TimeMCPServerImage = timeMCPServerImageURL + ":" + timeMCPServerImageTag + "@" + timeMCPServerImageDigest + redisImageURL = "redis" redisImageTag = "7-alpine" // RedisImage is used for Redis-backed session storage in scaling tests. diff --git a/test/e2e/proxy_stdio_test.go b/test/e2e/proxy_stdio_test.go index 3e7522d71d..cd281e0b0f 100644 --- a/test/e2e/proxy_stdio_test.go +++ b/test/e2e/proxy_stdio_test.go @@ -17,6 +17,7 @@ import ( "github.com/stacklok/toolhive/pkg/transport/types" "github.com/stacklok/toolhive/test/e2e" + "github.com/stacklok/toolhive/test/e2e/images" ) const ( @@ -183,7 +184,7 @@ var _ = Describe("Proxy Stdio E2E", Label("proxy", "stdio", "e2e"), Serial, func BeforeEach(func() { transportType = types.TransportTypeStdio proxyMode = "sse" - mcpServerName = "time" + mcpServerName = images.TimeMCPServerImage }) It("should proxy MCP requests successfully", func() { By("Getting time server URL") @@ -234,7 +235,7 @@ var _ = Describe("Proxy Stdio E2E", Label("proxy", "stdio", "e2e"), Serial, func BeforeEach(func() { transportType = types.TransportTypeStdio proxyMode = "streamable-http" - mcpServerName = "time" + mcpServerName = images.TimeMCPServerImage }) It("should proxy MCP requests successfully", func() { By("Getting time server URL") diff --git a/test/e2e/stdio_proxy_over_streamable_http_mcp_server_test.go b/test/e2e/stdio_proxy_over_streamable_http_mcp_server_test.go index 88a4a28309..a1c9c610c0 100644 --- a/test/e2e/stdio_proxy_over_streamable_http_mcp_server_test.go +++ b/test/e2e/stdio_proxy_over_streamable_http_mcp_server_test.go @@ -15,6 +15,7 @@ import ( . "github.com/onsi/gomega" "github.com/stacklok/toolhive/test/e2e" + "github.com/stacklok/toolhive/test/e2e/images" ) var _ = Describe("TimeStreamableHttpMcpServer", Label("proxy", "streamable-http", "e2e"), Serial, func() { @@ -44,8 +45,9 @@ var _ = Describe("TimeStreamableHttpMcpServer", Label("proxy", "streamable-http" By("Starting the time MCP server with streamable-http proxy") e2e.NewTHVCommand(config, "run", "--name", serverName, + "--transport", "stdio", "--proxy-mode", "streamable-http", - "time").ExpectSuccess() + images.TimeMCPServerImage).ExpectSuccess() By("Waiting for the server to be running") err := e2e.WaitForMCPServer(config, serverName, 60*time.Second)