From 1769f761cedf211ff19019c064647fbb5ce4e23b Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 12 Jun 2025 04:45:38 +0700 Subject: [PATCH] feat: improvements to e2e mcp and http tests --- apps/e2e/package.json | 6 +- .../src/__snapshots__/http-e2e.test.ts.snap | 292 +++++++++--------- .../src/__snapshots__/mcp-e2e.test.ts.snap | 119 ++++++- apps/e2e/src/http-fixtures.ts | 22 +- apps/e2e/src/mcp-e2e.test.ts | 3 +- apps/e2e/src/mcp-fixtures.ts | 145 ++++++++- apps/gateway/src/lib/durable-mcp-server.ts | 4 +- .../src/lib/resolve-http-edge-request.ts | 2 +- .../src/lib/resolve-origin-tool-call.ts | 32 +- apps/gateway/src/lib/types.ts | 2 +- packages/types/src/tools.ts | 17 +- 11 files changed, 458 insertions(+), 186 deletions(-) diff --git a/apps/e2e/package.json b/apps/e2e/package.json index 2c30aece..2f4dc9c4 100644 --- a/apps/e2e/package.json +++ b/apps/e2e/package.json @@ -15,9 +15,9 @@ "test": "run-s test:*", "test:lint": "eslint .", "test:typecheck": "tsc --noEmit", - "test-e2e": "vitest run", - "test-http-e2e": "vitest run src/http-e2e.test.ts", - "test-mcp-e2e": "vitest run src/mcp-e2e.test.ts" + "e2e": "vitest run", + "e2e-http": "vitest run src/http-e2e.test.ts", + "e2e-mcp": "vitest run src/mcp-e2e.test.ts" }, "dependencies": { "ky": "catalog:", diff --git a/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap b/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap index 3bc30d5c..eeb75685 100644 --- a/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap +++ b/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap @@ -1,42 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Basic GET caching > 3.0: GET @dev/test-basic-openapi@010332cf/getPost 1`] = ` -{ - "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque -iste corrupti reiciendis voluptatem eius rerum -sit cumque quod eligendi laborum minima -perferendis recusandae assumenda consectetur porro architecto ipsum ipsam", - "id": 13, - "title": "dolorum ut in voluptas mollitia et saepe quo animi", - "userId": 2, -} -`; - -exports[`Basic GET caching > 3.1: GET @dev/test-basic-openapi@010332cf/getPost?postId=13 1`] = ` -{ - "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque -iste corrupti reiciendis voluptatem eius rerum -sit cumque quod eligendi laborum minima -perferendis recusandae assumenda consectetur porro architecto ipsum ipsam", - "id": 13, - "title": "dolorum ut in voluptas mollitia et saepe quo animi", - "userId": 2, -} -`; - -exports[`Basic GET caching > 3.2: GET @dev/test-basic-openapi@010332cf/get_post?postId=13 1`] = ` -{ - "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque -iste corrupti reiciendis voluptatem eius rerum -sit cumque quod eligendi laborum minima -perferendis recusandae assumenda consectetur porro architecto ipsum ipsam", - "id": 13, - "title": "dolorum ut in voluptas mollitia et saepe quo animi", - "userId": 2, -} -`; - -exports[`Basic MCP origin "add" tool call success > 5.0: POST @dev/test-basic-mcp/add 1`] = ` +exports[`HTTP => MCP origin basic "add" tool call success > 5.0: POST @dev/test-basic-mcp/add 1`] = ` [ { "text": "42", @@ -45,7 +9,7 @@ exports[`Basic MCP origin "add" tool call success > 5.0: POST @dev/test-basic-mc ] `; -exports[`Basic MCP origin "add" tool call success > 5.1: GET @dev/test-basic-mcp/add 1`] = ` +exports[`HTTP => MCP origin basic "add" tool call success > 5.1: GET @dev/test-basic-mcp/add 1`] = ` [ { "text": "42", @@ -54,103 +18,7 @@ exports[`Basic MCP origin "add" tool call success > 5.1: GET @dev/test-basic-mcp ] `; -exports[`Basic OpenAPI getPost errors > 1.8: POST @dev/test-basic-openapi/getPost 1`] = ` -{ - "body": "quia et suscipit -suscipit recusandae consequuntur expedita et cum -reprehenderit molestiae ut ut quas totam -nostrum rerum est autem sunt rem eveniet architecto", - "id": 1, - "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "userId": 1, -} -`; - -exports[`Basic OpenAPI getPost errors > 1.9: GET @dev/test-basic-openapi/getPost 1`] = ` -{ - "body": "quia et suscipit -suscipit recusandae consequuntur expedita et cum -reprehenderit molestiae ut ut quas totam -nostrum rerum est autem sunt rem eveniet architecto", - "id": 1, - "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "userId": 1, -} -`; - -exports[`Basic OpenAPI getPost success > 0.0: POST @dev/test-basic-openapi/getPost 1`] = ` -{ - "body": "quia et suscipit -suscipit recusandae consequuntur expedita et cum -reprehenderit molestiae ut ut quas totam -nostrum rerum est autem sunt rem eveniet architecto", - "id": 1, - "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "userId": 1, -} -`; - -exports[`Basic OpenAPI getPost success > 0.1: POST @dev/test-basic-openapi@latest/getPost 1`] = ` -{ - "body": "quia et suscipit -suscipit recusandae consequuntur expedita et cum -reprehenderit molestiae ut ut quas totam -nostrum rerum est autem sunt rem eveniet architecto", - "id": 1, - "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "userId": 1, -} -`; - -exports[`Basic OpenAPI getPost success > 0.2: GET @dev/test-basic-openapi/getPost 1`] = ` -{ - "body": "quia et suscipit -suscipit recusandae consequuntur expedita et cum -reprehenderit molestiae ut ut quas totam -nostrum rerum est autem sunt rem eveniet architecto", - "id": 1, - "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "userId": 1, -} -`; - -exports[`Basic OpenAPI getPost success > 0.3: GET @dev/test-basic-openapi@010332cf/getPost?postId=1 1`] = ` -{ - "body": "quia et suscipit -suscipit recusandae consequuntur expedita et cum -reprehenderit molestiae ut ut quas totam -nostrum rerum est autem sunt rem eveniet architecto", - "id": 1, - "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "userId": 1, -} -`; - -exports[`Basic OpenAPI getPost success > 0.4: GET @dev/test-basic-openapi@010332cf/get_post?postId=1 1`] = ` -{ - "body": "quia et suscipit -suscipit recusandae consequuntur expedita et cum -reprehenderit molestiae ut ut quas totam -nostrum rerum est autem sunt rem eveniet architecto", - "id": 1, - "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "userId": 1, -} -`; - -exports[`Basic OpenAPI getPost success > 0.5: GET @dev/test-basic-openapi@010332cf/getPost 1`] = ` -{ - "body": "quia et suscipit -suscipit recusandae consequuntur expedita et cum -reprehenderit molestiae ut ut quas totam -nostrum rerum est autem sunt rem eveniet architecto", - "id": 1, - "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "userId": 1, -} -`; - -exports[`Basic POST caching > 4.0: POST @dev/test-basic-openapi@010332cf/get_post 1`] = ` +exports[`HTTP => OpenAPI origin basic GET caching > 3.0: GET @dev/test-basic-openapi@010332cf/getPost 1`] = ` { "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque iste corrupti reiciendis voluptatem eius rerum @@ -162,7 +30,7 @@ perferendis recusandae assumenda consectetur porro architecto ipsum ipsam", } `; -exports[`Basic POST caching > 4.1: POST @dev/test-basic-openapi@010332cf/get_post 1`] = ` +exports[`HTTP => OpenAPI origin basic GET caching > 3.1: GET @dev/test-basic-openapi@010332cf/getPost?postId=13 1`] = ` { "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque iste corrupti reiciendis voluptatem eius rerum @@ -174,7 +42,43 @@ perferendis recusandae assumenda consectetur porro architecto ipsum ipsam", } `; -exports[`Bypass caching > 2.0: GET @dev/test-basic-openapi@fc856666/getPost 1`] = ` +exports[`HTTP => OpenAPI origin basic GET caching > 3.2: GET @dev/test-basic-openapi@010332cf/get_post?postId=13 1`] = ` +{ + "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque +iste corrupti reiciendis voluptatem eius rerum +sit cumque quod eligendi laborum minima +perferendis recusandae assumenda consectetur porro architecto ipsum ipsam", + "id": 13, + "title": "dolorum ut in voluptas mollitia et saepe quo animi", + "userId": 2, +} +`; + +exports[`HTTP => OpenAPI origin basic POST caching > 4.0: POST @dev/test-basic-openapi@010332cf/get_post 1`] = ` +{ + "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque +iste corrupti reiciendis voluptatem eius rerum +sit cumque quod eligendi laborum minima +perferendis recusandae assumenda consectetur porro architecto ipsum ipsam", + "id": 13, + "title": "dolorum ut in voluptas mollitia et saepe quo animi", + "userId": 2, +} +`; + +exports[`HTTP => OpenAPI origin basic POST caching > 4.1: POST @dev/test-basic-openapi@010332cf/get_post 1`] = ` +{ + "body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque +iste corrupti reiciendis voluptatem eius rerum +sit cumque quod eligendi laborum minima +perferendis recusandae assumenda consectetur porro architecto ipsum ipsam", + "id": 13, + "title": "dolorum ut in voluptas mollitia et saepe quo animi", + "userId": 2, +} +`; + +exports[`HTTP => OpenAPI origin basic bypass caching > 2.0: GET @dev/test-basic-openapi@fc856666/getPost 1`] = ` { "body": "consectetur animi nesciunt iure dolore enim quia ad @@ -186,7 +90,7 @@ et est aut quod aut provident voluptas autem voluptas", } `; -exports[`Bypass caching > 2.1: GET @dev/test-basic-openapi@010332cf/getPost 1`] = ` +exports[`HTTP => OpenAPI origin basic bypass caching > 2.1: GET @dev/test-basic-openapi@010332cf/getPost 1`] = ` { "body": "consectetur animi nesciunt iure dolore enim quia ad @@ -198,7 +102,7 @@ et est aut quod aut provident voluptas autem voluptas", } `; -exports[`Bypass caching > 2.2: GET @dev/test-basic-openapi@010332cf/getPost?postId=9 1`] = ` +exports[`HTTP => OpenAPI origin basic bypass caching > 2.2: GET @dev/test-basic-openapi@010332cf/getPost?postId=9 1`] = ` { "body": "consectetur animi nesciunt iure dolore enim quia ad @@ -210,7 +114,7 @@ et est aut quod aut provident voluptas autem voluptas", } `; -exports[`Bypass caching > 2.3: GET @dev/test-basic-openapi@010332cf/get_post?postId=9 1`] = ` +exports[`HTTP => OpenAPI origin basic bypass caching > 2.3: GET @dev/test-basic-openapi@010332cf/get_post?postId=9 1`] = ` { "body": "consectetur animi nesciunt iure dolore enim quia ad @@ -222,7 +126,7 @@ et est aut quod aut provident voluptas autem voluptas", } `; -exports[`Bypass caching > 2.4: GET @dev/test-basic-openapi@010332cf/get_post?postId=9 1`] = ` +exports[`HTTP => OpenAPI origin basic bypass caching > 2.4: GET @dev/test-basic-openapi@010332cf/get_post?postId=9 1`] = ` { "body": "consectetur animi nesciunt iure dolore enim quia ad @@ -234,7 +138,7 @@ et est aut quod aut provident voluptas autem voluptas", } `; -exports[`Bypass caching > 2.5: GET @dev/test-basic-openapi@010332cf/get_post?postId=9 1`] = ` +exports[`HTTP => OpenAPI origin basic bypass caching > 2.5: GET @dev/test-basic-openapi@010332cf/get_post?postId=9 1`] = ` { "body": "consectetur animi nesciunt iure dolore enim quia ad @@ -246,18 +150,114 @@ et est aut quod aut provident voluptas autem voluptas", } `; -exports[`OpenAPI kitchen echo tool with empty body > 9.0: POST @dev/test-everything-openapi/echo 1`] = `{}`; +exports[`HTTP => OpenAPI origin basic getPost errors > 1.8: POST @dev/test-basic-openapi/getPost 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; -exports[`OpenAPI kitchen echo tool with empty body > 9.1: POST @dev/test-everything-openapi/echo 1`] = `{}`; +exports[`HTTP => OpenAPI origin basic getPost errors > 1.9: GET @dev/test-basic-openapi/getPost 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; -exports[`OpenAPI kitchen sink pure tool > 7.0: POST @dev/test-everything-openapi/pure 1`] = ` +exports[`HTTP => OpenAPI origin basic getPost success > 0.0: POST @dev/test-basic-openapi/getPost 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; + +exports[`HTTP => OpenAPI origin basic getPost success > 0.1: POST @dev/test-basic-openapi@latest/getPost 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; + +exports[`HTTP => OpenAPI origin basic getPost success > 0.2: GET @dev/test-basic-openapi/getPost 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; + +exports[`HTTP => OpenAPI origin basic getPost success > 0.3: GET @dev/test-basic-openapi@010332cf/getPost?postId=1 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; + +exports[`HTTP => OpenAPI origin basic getPost success > 0.4: GET @dev/test-basic-openapi@010332cf/get_post?postId=1 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; + +exports[`HTTP => OpenAPI origin basic getPost success > 0.5: GET @dev/test-basic-openapi@010332cf/getPost 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; + +exports[`HTTP => OpenAPI origin everything "echo" tool with empty body > 9.0: POST @dev/test-everything-openapi/echo 1`] = `{}`; + +exports[`HTTP => OpenAPI origin everything "echo" tool with empty body > 9.1: POST @dev/test-everything-openapi/echo 1`] = `{}`; + +exports[`HTTP => OpenAPI origin everything "pure" tool > 7.0: POST @dev/test-everything-openapi/pure 1`] = ` { "foo": "bar", "nala": "kitten", } `; -exports[`OpenAPI kitchen sink pure tool > 7.1: POST @dev/test-everything-openapi/pure 1`] = ` +exports[`HTTP => OpenAPI origin everything "pure" tool > 7.1: POST @dev/test-everything-openapi/pure 1`] = ` { "foo": "bar", "nala": "kitten", diff --git a/apps/e2e/src/__snapshots__/mcp-e2e.test.ts.snap b/apps/e2e/src/__snapshots__/mcp-e2e.test.ts.snap index c2296126..e35a4645 100644 --- a/apps/e2e/src/__snapshots__/mcp-e2e.test.ts.snap +++ b/apps/e2e/src/__snapshots__/mcp-e2e.test.ts.snap @@ -1,6 +1,17 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Basic MCP => OpenAPI @ 010332cf get_post success > 2.0: @dev/test-basic-openapi@010332cf/mcp get_post 1`] = ` +exports[`MCP => MCP origin basic "add" tool call success > 9.0: @dev/test-basic-mcp/mcp add 1`] = ` +{ + "content": [ + { + "text": "62", + "type": "text", + }, + ], +} +`; + +exports[`MCP => OpenAPI origin basic @ 010332cf get_post success > 2.0: @dev/test-basic-openapi@010332cf/mcp get_post 1`] = ` { "content": [], "isError": false, @@ -16,7 +27,7 @@ ipsam ut commodi dolor voluptatum modi aut vitae", } `; -exports[`Basic MCP => OpenAPI @ latest get_post success > 1.0: @dev/test-basic-openapi@latest/mcp get_post 1`] = ` +exports[`MCP => OpenAPI origin basic @ latest get_post success > 1.0: @dev/test-basic-openapi@latest/mcp get_post 1`] = ` { "content": [], "isError": false, @@ -32,17 +43,71 @@ molestiae porro eius odio et labore et velit aut", } `; -exports[`Basic MCP => OpenAPI everything errors > 5.0: @dev/test-everything-openapi/mcp strict_additional_properties 1`] = ` +exports[`MCP => OpenAPI origin basic bypass caching > 6.0: @dev/test-basic-openapi@fc856666/mcp get_post 1`] = ` { "content": [], "isError": false, "structuredContent": { - "foo": "bar", + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, }, } `; -exports[`Basic MCP => OpenAPI get_post errors > 4.3: @dev/test-basic-openapi/mcp get_post 1`] = ` +exports[`MCP => OpenAPI origin basic caching > 7.0: @dev/test-basic-openapi@010332cf/mcp get_post 1`] = ` +{ + "content": [], + "isError": false, + "structuredContent": { + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, + }, +} +`; + +exports[`MCP => OpenAPI origin basic caching > 7.1: @dev/test-basic-openapi@010332cf/mcp get_post 1`] = ` +{ + "content": [], + "isError": false, + "structuredContent": { + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, + }, +} +`; + +exports[`MCP => OpenAPI origin basic caching > 7.2: @dev/test-basic-openapi@010332cf/mcp get_post 1`] = ` +{ + "content": [], + "isError": false, + "structuredContent": { + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, + }, +} +`; + +exports[`MCP => OpenAPI origin basic get_post errors > 4.3: @dev/test-basic-openapi/mcp get_post 1`] = ` { "content": [], "isError": false, @@ -58,7 +123,7 @@ sunt ut sequi eos ea sed quas", } `; -exports[`Basic MCP => OpenAPI get_post success > 0.0: @dev/test-basic-openapi/mcp get_post 1`] = ` +exports[`MCP => OpenAPI origin basic get_post success > 0.0: @dev/test-basic-openapi/mcp get_post 1`] = ` { "content": [], "isError": false, @@ -73,3 +138,45 @@ nostrum rerum est autem sunt rem eveniet architecto", }, } `; + +exports[`MCP => OpenAPI origin basic normalized caching > 8.0: @dev/test-basic-openapi@010332cf/mcp get_post 1`] = ` +{ + "content": [], + "isError": false, + "structuredContent": { + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, + }, +} +`; + +exports[`MCP => OpenAPI origin basic normalized caching > 8.1: @dev/test-basic-openapi@010332cf/mcp get_post 1`] = ` +{ + "content": [], + "isError": false, + "structuredContent": { + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, + }, +} +`; + +exports[`MCP => OpenAPI origin everything errors > 5.0: @dev/test-everything-openapi/mcp strict_additional_properties 1`] = ` +{ + "content": [], + "isError": false, + "structuredContent": { + "foo": "bar", + }, +} +`; diff --git a/apps/e2e/src/http-fixtures.ts b/apps/e2e/src/http-fixtures.ts index b778055a..64bb0d46 100644 --- a/apps/e2e/src/http-fixtures.ts +++ b/apps/e2e/src/http-fixtures.ts @@ -58,7 +58,7 @@ const now = Date.now() export const fixtureSuites: E2ETestFixtureSuite[] = [ { - title: 'Basic OpenAPI getPost success', + title: 'HTTP => OpenAPI origin basic getPost success', compareResponseBodies: true, fixtures: [ { @@ -107,7 +107,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'Basic OpenAPI getPost errors', + title: 'HTTP => OpenAPI origin basic getPost errors', fixtures: [ { path: '@dev/test-basic-openapi/getPost', @@ -248,7 +248,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'Bypass caching', + title: 'HTTP => OpenAPI origin basic bypass caching', compareResponseBodies: true, fixtures: [ { @@ -340,7 +340,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'Basic GET caching', + title: 'HTTP => OpenAPI origin basic GET caching', compareResponseBodies: true, sequential: true, fixtures: [ @@ -390,7 +390,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'Basic POST caching', + title: 'HTTP => OpenAPI origin basic POST caching', compareResponseBodies: true, sequential: true, fixtures: [ @@ -422,7 +422,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'Basic MCP origin "add" tool call success', + title: 'HTTP => MCP origin basic "add" tool call success', compareResponseBodies: true, fixtures: [ { @@ -453,7 +453,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'Basic MCP origin "echo" tool call success', + title: 'HTTP => MCP origin basic "echo" tool call success', snapshot: false, fixtures: [ { @@ -500,7 +500,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'OpenAPI kitchen sink pure tool', + title: 'HTTP => OpenAPI origin everything "pure" tool', sequential: true, compareResponseBodies: true, fixtures: [ @@ -549,7 +549,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'OpenAPI kitchen sink disabled tool', + title: 'HTTP => OpenAPI origin everything "disabled_tool"', fixtures: [ { path: '@dev/test-everything-openapi/disabled_tool', @@ -563,7 +563,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'OpenAPI kitchen echo tool with empty body', + title: 'HTTP => OpenAPI origin everything "echo" tool with empty body', compareResponseBodies: true, fixtures: [ { @@ -588,7 +588,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ ] }, { - title: 'OpenAPI kitchen sink unpure_marked_pure tool', + title: 'HTTP => OpenAPI origin everything "unpure_marked_pure" tool', compareResponseBodies: true, snapshot: false, fixtures: [ diff --git a/apps/e2e/src/mcp-e2e.test.ts b/apps/e2e/src/mcp-e2e.test.ts index e69a16e4..cc2fafe5 100644 --- a/apps/e2e/src/mcp-e2e.test.ts +++ b/apps/e2e/src/mcp-e2e.test.ts @@ -82,7 +82,8 @@ for (const [i, fixtureSuite] of fixtureSuites.entries()) { async () => { const result = await client.callTool({ name: toolName, - arguments: fixture.request.args + arguments: fixture.request.args, + _meta: fixture.request._meta }) if (debugFixture) { diff --git a/apps/e2e/src/mcp-fixtures.ts b/apps/e2e/src/mcp-fixtures.ts index 10f04d63..4a29bb2b 100644 --- a/apps/e2e/src/mcp-fixtures.ts +++ b/apps/e2e/src/mcp-fixtures.ts @@ -11,6 +11,7 @@ export type MCPE2ETestFixture = { request: { name: string args: Record + _meta?: Record } response?: { @@ -63,7 +64,7 @@ const now = Date.now() export const fixtureSuites: MCPE2ETestFixtureSuite[] = [ { - title: 'Basic MCP => OpenAPI get_post success', + title: 'MCP => OpenAPI origin basic get_post success', path: '@dev/test-basic-openapi/mcp', fixtures: [ { @@ -77,7 +78,7 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [ ] }, { - title: 'Basic MCP => OpenAPI @ latest get_post success ', + title: 'MCP => OpenAPI origin basic @ latest get_post success ', path: '@dev/test-basic-openapi@latest/mcp', fixtures: [ { @@ -91,7 +92,7 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [ ] }, { - title: 'Basic MCP => OpenAPI @ 010332cf get_post success ', + title: 'MCP => OpenAPI origin basic @ 010332cf get_post success ', path: '@dev/test-basic-openapi@010332cf/mcp', fixtures: [ { @@ -105,7 +106,7 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [ ] }, { - title: 'Basic MCP => MCP "echo" tool call success', + title: 'MCP => MCP origin basic "echo" tool call success', path: '@dev/test-basic-mcp/mcp', stableSnapshot: false, fixtures: [ @@ -152,7 +153,7 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [ ] }, { - title: 'Basic MCP => OpenAPI get_post errors', + title: 'MCP => OpenAPI origin basic get_post errors', path: '@dev/test-basic-openapi/mcp', fixtures: [ { @@ -219,7 +220,7 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [ ] }, { - title: 'Basic MCP => OpenAPI everything errors', + title: 'MCP => OpenAPI origin everything errors', path: '@dev/test-everything-openapi/mcp', fixtures: [ { @@ -251,5 +252,137 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [ } } ] + }, + { + title: 'MCP => OpenAPI origin basic bypass caching', + path: '@dev/test-basic-openapi@fc856666/mcp', + fixtures: [ + { + // ensure we bypass the cache for requests for tools which do not have + // a custom `pure` or `cacheControl` set in their tool config. + request: { + name: 'get_post', + args: { + postId: 1 + } + }, + response: { + isError: false, + _agenticMeta: { + cacheStatus: 'BYPASS' + } + } + } + ] + }, + { + title: 'MCP => OpenAPI origin basic caching', + path: '@dev/test-basic-openapi@010332cf/mcp', + fixtures: [ + { + request: { + name: 'get_post', + args: { + postId: 1 + } + }, + response: { + isError: false + } + }, + { + request: { + name: 'get_post', + args: { + postId: 1 + } + }, + response: { + isError: false, + _agenticMeta: { + // second request should hit the cache + cacheStatus: 'HIT' + } + } + }, + { + request: { + name: 'get_post', + args: { + postId: 1 + }, + // disable caching via a custom metadata cache-control header + _meta: { + agentic: { + headers: { + 'cache-control': 'no-store' + } + } + } + }, + response: { + isError: false, + _agenticMeta: { + cacheStatus: 'BYPASS' + } + } + } + ] + }, + { + title: 'MCP => OpenAPI origin basic normalized caching', + path: '@dev/test-basic-openapi@010332cf/mcp', + fixtures: [ + { + request: { + name: 'get_post', + args: { + postId: 1, + foo: true, + nala: 'kitten' + } + }, + response: { + isError: false + } + }, + { + request: { + name: 'get_post', + args: { + foo: true, + postId: 1, + nala: 'kitten' + } + }, + response: { + isError: false, + _agenticMeta: { + // second request should hit the cache even though the args are in a + // different order + cacheStatus: 'HIT' + } + } + } + ] + }, + { + title: 'MCP => MCP origin basic "add" tool call success', + path: '@dev/test-basic-mcp/mcp', + fixtures: [ + { + request: { + name: 'add', + args: { + a: 13, + b: 49 + } + }, + response: { + isError: false, + content: [{ type: 'text', text: '62' }] + } + } + ] } ] diff --git a/apps/gateway/src/lib/durable-mcp-server.ts b/apps/gateway/src/lib/durable-mcp-server.ts index 0f5d1d3f..043afe5a 100644 --- a/apps/gateway/src/lib/durable-mcp-server.ts +++ b/apps/gateway/src/lib/durable-mcp-server.ts @@ -86,10 +86,11 @@ export class DurableMcpServerBase extends McpAgent< })) server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name: toolName, arguments: args } = request.params + const { name: toolName, arguments: args, _meta } = request.params const sessionId = this.ctx.id.toString() const tool = tools.find((tool) => tool.name === toolName) + const cacheControl = (_meta?.agentic as any)?.headers?.['cache-control'] let resolvedOriginToolCallResult: ResolvedOriginToolCallResult | undefined let toolCallResponse: McpToolCallResponse | undefined @@ -102,6 +103,7 @@ export class DurableMcpServerBase extends McpAgent< deployment, consumer, pricingPlan, + cacheControl, sessionId, env: this.env, ip, diff --git a/apps/gateway/src/lib/resolve-http-edge-request.ts b/apps/gateway/src/lib/resolve-http-edge-request.ts index 41b2c58b..192ee48f 100644 --- a/apps/gateway/src/lib/resolve-http-edge-request.ts +++ b/apps/gateway/src/lib/resolve-http-edge-request.ts @@ -38,7 +38,7 @@ export async function resolveHttpEdgeRequest( const cacheControl = isRequestPubliclyCacheable(ctx.req.raw) ? ctx.req.header('cache-control') - : 'no-cache' + : 'no-store' const { method } = ctx.req const requestUrl = new URL(ctx.req.url) diff --git a/apps/gateway/src/lib/resolve-origin-tool-call.ts b/apps/gateway/src/lib/resolve-origin-tool-call.ts index bcf662f6..e7370db9 100644 --- a/apps/gateway/src/lib/resolve-origin-tool-call.ts +++ b/apps/gateway/src/lib/resolve-origin-tool-call.ts @@ -63,6 +63,7 @@ export async function resolveOriginToolCall({ const numRequestsCost = 1 let rateLimitResult: RateLimitResult | undefined let rateLimit: RateLimit | undefined | null + let cacheStatus: CacheStatus | undefined let reportUsage = true // Resolve rate limit and whether to report `requests` usage based on the @@ -100,7 +101,15 @@ export async function resolveOriginToolCall({ rateLimit = toolConfig.rateLimit as RateLimit } - if (!cacheControl) { + if (cacheControl) { + if (!isCacheControlPubliclyCacheable(cacheControl)) { + // Incoming request explicitly requests to bypass the gateway's cache. + cacheStatus = 'BYPASS' + } else { + // TODO: Should we allow incoming cache-control headers to override the + // gateway's cache behavior? + } + } else { // If the incoming request doesn't specify a desired `cache-control`, // then use a default based on the tool's configured settings. if (toolConfig.cacheControl !== undefined) { @@ -113,6 +122,7 @@ export async function resolveOriginToolCall({ } else { // Default to not caching any responses. cacheControl = 'no-store' + cacheStatus = 'DYNAMIC' } } @@ -145,8 +155,19 @@ export async function resolveOriginToolCall({ assert(toolConfig.enabled, 404, `Tool "${tool.name}" is disabled`) } } else { - // Default to not caching any responses. - cacheControl ??= 'no-store' + if (cacheControl) { + if (!isCacheControlPubliclyCacheable(cacheControl)) { + // Incoming request explicitly requests to bypass the gateway's cache. + cacheStatus = 'BYPASS' + } else { + // TODO: Should we allow incoming cache-control headers to override the + // gateway's cache behavior? + } + } else { + // Default to not caching any responses. + cacheControl = 'no-store' + cacheStatus = 'DYNAMIC' + } } if (rateLimit) { @@ -213,8 +234,9 @@ export async function resolveOriginToolCall({ // Fetch the origin response without caching (useful for debugging) // const originResponse = await fetch(originRequest) - const cacheStatus = + cacheStatus = (originResponse.headers.get('cf-cache-status') as CacheStatus) ?? + cacheStatus ?? (cacheKey ? 'MISS' : 'BYPASS') return { @@ -318,7 +340,7 @@ export async function resolveOriginToolCall({ } return { - cacheStatus: cacheKey ? 'MISS' : 'BYPASS', + cacheStatus: cacheStatus ?? (cacheKey ? 'MISS' : 'BYPASS'), reportUsage, rateLimitResult, toolCallArgs, diff --git a/apps/gateway/src/lib/types.ts b/apps/gateway/src/lib/types.ts index 0d553281..2970f4e0 100644 --- a/apps/gateway/src/lib/types.ts +++ b/apps/gateway/src/lib/types.ts @@ -54,7 +54,7 @@ export type RateLimitState = { export type RateLimitCache = Map -export type CacheStatus = 'HIT' | 'MISS' | 'BYPASS' +export type CacheStatus = 'HIT' | 'MISS' | 'BYPASS' | 'DYNAMIC' export type RequestMode = 'mcp' | 'http' export type WaitUntil = (promise: Promise) => void diff --git a/packages/types/src/tools.ts b/packages/types/src/tools.ts index 91d4904d..4fb83d96 100644 --- a/packages/types/src/tools.ts +++ b/packages/types/src/tools.ts @@ -125,13 +125,20 @@ export const toolConfigSchema = z pure: z.boolean().optional().default(false), /** - * A `Cache-Control` header value to use for caching this tool's responses. + * A custom `Cache-Control` header to use for caching this tool's responses. * - * If `pure` is `true`, this defaults to: `public, max-age=31560000, s-maxage=31560000, stale-while-revalidate=3600` (cache publicly for up to 1 year). + * If set, this field overrides `pure`. * - * If `pure` is `false`, this defaults to the origin server's - * `cache-control` header value. If the origin server does not set a - * `cache-control` header, it defaults to `no-store`. + * If not set and `pure` is `true`, the gateway will default to: + * `public, max-age=31560000, s-maxage=31560000, stale-while-revalidate=3600` + * (cache publicly for up to 1 year). + * + * If not set and `pure` is `false`, the gateway will default to `no-store` + * which will disable caching. This is the default gateway behavior for + * tools (no caching). + * + * Note that origin server response headers may also choose to disable + * caching on a per-request basis. * * @default undefined */