From 4ebfcd35d5db4ec35e232f59d26c4f1e09d18667 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Wed, 11 Jun 2025 15:50:30 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/__snapshots__/http-e2e.test.ts.snap | 18 +++++ apps/e2e/src/http-fixtures.ts | 80 +++++++++++++++++++ apps/e2e/src/mcp-fixtures.ts | 1 - .../src/lib/get-tool-args-from-request.ts | 14 +++- .../src/lib/resolve-origin-tool-call.ts | 6 +- readme.md | 1 + 6 files changed, 112 insertions(+), 8 deletions(-) diff --git a/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap b/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap index f09c5aff..60fa2ae8 100644 --- a/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap +++ b/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap @@ -246,6 +246,10 @@ 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[`OpenAPI kitchen echo tool with empty body > 9.1: POST @dev/test-everything-openapi/echo 1`] = `{}`; + exports[`OpenAPI kitchen sink pure tool > 7.0: POST @dev/test-everything-openapi/pure 1`] = ` { "foo": "bar", @@ -259,3 +263,17 @@ exports[`OpenAPI kitchen sink pure tool > 7.1: POST @dev/test-everything-openapi "nala": "kitten", } `; + +exports[`OpenAPI kitchen sink unpure_marked_pure tool > 10.0: POST @dev/test-everything-openapi/unpure_marked_pure 1`] = ` +{ + "nala": "cat", + "now": 1749631781793, +} +`; + +exports[`OpenAPI kitchen sink unpure_marked_pure tool > 10.1: POST @dev/test-everything-openapi/unpure_marked_pure 1`] = ` +{ + "nala": "cat", + "now": 1749631781793, +} +`; diff --git a/apps/e2e/src/http-fixtures.ts b/apps/e2e/src/http-fixtures.ts index 50092840..0d879df1 100644 --- a/apps/e2e/src/http-fixtures.ts +++ b/apps/e2e/src/http-fixtures.ts @@ -1,3 +1,5 @@ +import { expect } from 'vitest' + export type E2ETestFixture = { path: string @@ -554,5 +556,83 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ } } ] + }, + { + title: 'OpenAPI kitchen sink disabled tool', + fixtures: [ + { + path: '@dev/test-everything-openapi/disabled_tool', + request: { + method: 'POST' + }, + response: { + status: 404 + } + } + ] + }, + { + title: 'OpenAPI kitchen echo tool with empty body', + compareResponseBodies: true, + fixtures: [ + { + path: '@dev/test-everything-openapi/echo', + request: { + method: 'POST' + }, + response: { + body: {} + } + }, + { + path: '@dev/test-everything-openapi/echo', + request: { + method: 'POST', + json: {} + }, + response: { + body: {} + } + } + ] + }, + { + title: 'OpenAPI kitchen sink unpure_marked_pure tool', + compareResponseBodies: true, + fixtures: [ + { + path: '@dev/test-everything-openapi/unpure_marked_pure', + request: { + method: 'POST', + json: { + nala: 'cat' + } + }, + response: { + validate: (body) => { + expect(body?.nala).toEqual('cat') + expect(typeof body.now).toBe('number') + expect(body.now).toBeGreaterThan(0) + } + } + }, + { + path: '@dev/test-everything-openapi/unpure_marked_pure', + request: { + method: 'POST', + json: { + nala: 'cat' + } + }, + response: { + headers: { + 'cf-cache-status': 'HIT' + } + } + // compareResponseBodies should result in the same cached response body, + // even though the origin would return a different `now` value if it + // weren't marked `pure`. + } + ] } ] diff --git a/apps/e2e/src/mcp-fixtures.ts b/apps/e2e/src/mcp-fixtures.ts index a1b1a299..16e54ca4 100644 --- a/apps/e2e/src/mcp-fixtures.ts +++ b/apps/e2e/src/mcp-fixtures.ts @@ -49,7 +49,6 @@ export const fixtureSuites: MCPE2ETestFixtureSuite[] = [ { title: 'Basic MCP => OpenAPI get_post success', path: '@dev/test-basic-openapi/mcp', - // debug: true, fixtures: [ { request: { diff --git a/apps/gateway/src/lib/get-tool-args-from-request.ts b/apps/gateway/src/lib/get-tool-args-from-request.ts index 7b3bd969..1ca1c5ab 100644 --- a/apps/gateway/src/lib/get-tool-args-from-request.ts +++ b/apps/gateway/src/lib/get-tool-args-from-request.ts @@ -43,10 +43,16 @@ export async function getToolArgsFromRequest( return incomingRequestArgs } else if (request.method === 'POST') { - const incomingRequestArgsRaw = (await request.clone().json()) as Record< - string, - any - > + let incomingRequestArgsRaw: unknown = {} + + // TODO: verify content-type of request is application/json + + try { + incomingRequestArgsRaw = (await request.json()) as Record + } catch { + // If the request body is not JSON or malformed, ignore it for now. + // TODO: need to improve on this logic. + } // TODO: Proper support for empty params with POST requests assert(incomingRequestArgsRaw, 400, 'Invalid empty request body') diff --git a/apps/gateway/src/lib/resolve-origin-tool-call.ts b/apps/gateway/src/lib/resolve-origin-tool-call.ts index 996cc801..05d493c5 100644 --- a/apps/gateway/src/lib/resolve-origin-tool-call.ts +++ b/apps/gateway/src/lib/resolve-origin-tool-call.ts @@ -126,11 +126,11 @@ export async function resolveOriginToolCall({ if (pricingPlanToolOverride.enabled !== undefined) { assert( pricingPlanToolOverride.enabled, - 403, + toolConfig.enabled ? 403 : 404, `Tool "${tool.name}" is disabled for pricing plan "${pricingPlan.slug}"` ) } else { - assert(toolConfig.enabled, 403, `Tool "${tool.name}" is disabled`) + assert(toolConfig.enabled, 404, `Tool "${tool.name}" is disabled`) } if (pricingPlanToolOverride.reportUsage !== undefined) { @@ -142,7 +142,7 @@ export async function resolveOriginToolCall({ rateLimit = pricingPlanToolOverride.rateLimit as RateLimit } } else { - assert(toolConfig.enabled, 403, `Tool "${tool.name}" is disabled`) + assert(toolConfig.enabled, 404, `Tool "${tool.name}" is disabled`) } } else { // Default to not caching any responses. diff --git a/readme.md b/readme.md index 5a49ce11..bf376894 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,7 @@ - oauth flow - https://docs.scalekit.com/guides/mcp/oauth - openapi-kitchen-sink + - add more test cases to e2e tests - mcp-kitchen-sink - how to handle binary bodies and responses? - improve logger vs console for non-hono path and util methods