From 4a49cd3e42230a2dba575c0947f1f0de751b2bc6 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Wed, 25 Jun 2025 02:30:22 -0500 Subject: [PATCH] feat: add @agentic/search to http e2e fixtures --- apps/api/.env.example | 7 +++- .../api-v1/deployments/create-deployment.ts | 9 +++- .../api/src/api-v1/projects/create-project.ts | 23 ++++++----- apps/api/src/lib/env.ts | 9 +++- apps/e2e/src/http-e2e.test.ts | 2 +- apps/e2e/src/http-fixtures.ts | 19 +++++++++ examples/search/package.json | 1 - examples/search/src/worker.ts | 41 ++++++++++++++----- examples/search/wrangler.jsonc | 6 ++- pnpm-lock.yaml | 3 -- readme.md | 2 +- 11 files changed, 91 insertions(+), 31 deletions(-) diff --git a/apps/api/.env.example b/apps/api/.env.example index 22e87e4d..66b04319 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -21,6 +21,11 @@ STRIPE_WEBHOOK_SECRET= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= +RESEND_API_KEY= + +# Used to make admin API calls from the API gateway AGENTIC_ADMIN_API_KEY= -RESEND_API_KEY= +# Used to simplify recreating the demo `@agentic/search` project during +# development while we're frequently resetting the database +AGENTIC_SEARCH_PROXY_SECRET= diff --git a/apps/api/src/api-v1/deployments/create-deployment.ts b/apps/api/src/api-v1/deployments/create-deployment.ts index 508e571a..0984685f 100644 --- a/apps/api/src/api-v1/deployments/create-deployment.ts +++ b/apps/api/src/api-v1/deployments/create-deployment.ts @@ -84,6 +84,13 @@ export function registerV1CreateDeployment( user.username === 'agentic' ) + // Used to simplify recreating the demo `@agentic/search` project during + // development while we're frequently resetting the database + const secret = + projectIdentifier === '@agentic/search' + ? env.AGENTIC_SEARCH_PROXY_SECRET + : await sha256() + // Upsert the project if it doesn't already exist // The typecast is necessary here because we're not populating the // lastPublishedDeployment, but that's fine because it's a new project @@ -98,7 +105,7 @@ export function registerV1CreateDeployment( userId: user.id, teamId: teamMember?.teamId, private: isPrivate, - _secret: await sha256() + _secret: secret }) .returning() )[0] as typeof project diff --git a/apps/api/src/api-v1/projects/create-project.ts b/apps/api/src/api-v1/projects/create-project.ts index 47729128..f9456c1b 100644 --- a/apps/api/src/api-v1/projects/create-project.ts +++ b/apps/api/src/api-v1/projects/create-project.ts @@ -55,12 +55,8 @@ export function registerV1CreateProject( const teamMember = c.get('teamMember') const namespace = teamMember ? teamMember.teamSlug : user.username const identifier = `@${namespace}/${body.name}` - const parsedProjectIdentifier = parseProjectIdentifier(identifier) - assert( - parsedProjectIdentifier, - 400, - `Invalid project identifier "${identifier}"` - ) + const { projectIdentifier, projectNamespace, projectName } = + parseProjectIdentifier(identifier) // Used for testing e2e fixtures in the development marketplace const isPrivate = !( @@ -68,17 +64,24 @@ export function registerV1CreateProject( user.username === 'agentic' ) + // Used to simplify recreating the demo `@agentic/search` project during + // development while we're frequently resetting the database + const secret = + projectIdentifier === '@agentic/search' + ? env.AGENTIC_SEARCH_PROXY_SECRET + : await sha256() + const [project] = await db .insert(schema.projects) .values({ ...body, - identifier: parsedProjectIdentifier.projectIdentifier, - namespace: parsedProjectIdentifier.projectNamespace, - name: parsedProjectIdentifier.projectName, + identifier: projectIdentifier, + namespace: projectNamespace, + name: projectName, teamId: teamMember?.teamId, userId: user.id, private: isPrivate, - _secret: await sha256() + _secret: secret }) .returning() assert(project, 500, `Failed to create project "${body.name}"`) diff --git a/apps/api/src/lib/env.ts b/apps/api/src/lib/env.ts index 54acee61..3709a754 100644 --- a/apps/api/src/lib/env.ts +++ b/apps/api/src/lib/env.ts @@ -15,7 +15,7 @@ export const envSchema = baseEnvSchema JWT_SECRET: z.string().nonempty(), - PORT: z.number().default(3001), + PORT: z.coerce.number().default(3001), STRIPE_SECRET_KEY: z.string().nonempty(), STRIPE_WEBHOOK_SECRET: z.string().nonempty(), @@ -23,9 +23,14 @@ export const envSchema = baseEnvSchema GITHUB_CLIENT_ID: z.string().nonempty(), GITHUB_CLIENT_SECRET: z.string().nonempty(), + RESEND_API_KEY: z.string().nonempty(), + + // Used to make admin API calls from the API gateway AGENTIC_ADMIN_API_KEY: z.string().nonempty(), - RESEND_API_KEY: z.string().nonempty() + // Used to simplify recreating the demo `@agentic/search` project during + // development while we're frequently resetting the database + AGENTIC_SEARCH_PROXY_SECRET: z.string().nonempty() }) .strip() export type RawEnv = z.infer diff --git a/apps/e2e/src/http-e2e.test.ts b/apps/e2e/src/http-e2e.test.ts index f0de8a1e..aa354bee 100644 --- a/apps/e2e/src/http-e2e.test.ts +++ b/apps/e2e/src/http-e2e.test.ts @@ -137,8 +137,8 @@ for (const [i, fixtureSuite] of fixtureSuites.entries()) { if (debugFixture) { console.log( `${repeatIterationPrefix}${fixtureName} => ${res.status}`, + body, { - body, headers: Object.fromEntries(res.headers.entries()) } ) diff --git a/apps/e2e/src/http-fixtures.ts b/apps/e2e/src/http-fixtures.ts index 98231155..89d103fc 100644 --- a/apps/e2e/src/http-fixtures.ts +++ b/apps/e2e/src/http-fixtures.ts @@ -716,5 +716,24 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ } } ] + }, + { + title: 'HTTP => Production MCP origin "search" tool', + // NOTE: this one actually hits a production service and costs a small + // amount of $ per request. + fixtures: [ + { + path: '@agentic/search/search', + request: { + method: 'POST', + json: { + query: 'latest ai news' + } + }, + response: { + snapshot: false + } + } + ] } ] diff --git a/examples/search/package.json b/examples/search/package.json index a5f9be9d..bf6f7d79 100644 --- a/examples/search/package.json +++ b/examples/search/package.json @@ -18,7 +18,6 @@ }, "dependencies": { "@agentic/platform": "workspace:*", - "@agentic/platform-core": "workspace:*", "@agentic/serper": "catalog:", "@hono/mcp": "catalog:", "@modelcontextprotocol/sdk": "catalog:", diff --git a/examples/search/src/worker.ts b/examples/search/src/worker.ts index e432fa43..5db4c03d 100644 --- a/examples/search/src/worker.ts +++ b/examples/search/src/worker.ts @@ -1,4 +1,3 @@ -import { assert } from '@agentic/platform-core' import { SerperClient } from '@agentic/serper' import { StreamableHTTPTransport } from '@hono/mcp' import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' @@ -59,21 +58,42 @@ export default { .optional() .describe('Type of Google search to perform') }).shape, - outputSchema: z.object({}).passthrough().shape + outputSchema: z + .object({ + results: z.any(), + answerBox: z.any().optional(), + knowledgeGraph: z.any().optional(), + images: z.any().optional(), + videos: z.any().optional(), + places: z.any().optional(), + news: z.any().optional(), + shopping: z.any().optional() + }) + .passthrough().shape }, async (args, { _meta }) => { + console.log('search call', { + args, + _meta + }) + // Make sure the request is coming from Agentic - assert( - (_meta?.agentic as any)?.agenticProxySecret === - parsedEnv.AGENTIC_PROXY_SECRET, - 400, - 'Invalid request' - ) + if ( + (_meta?.agentic as any)?.agenticProxySecret !== + parsedEnv.AGENTIC_PROXY_SECRET + ) { + return { + content: [], + structuredContent: { + error: 'Invalid request' + } + } + } const result: any = await serper!.search({ q: args.query, - num: args.num, - type: args.type + num: args.num ?? 5, + type: args.type ?? 'search' }) // Simplify search results to optimize for LLM usage @@ -83,6 +103,7 @@ export default { delete result.peopleAlsoAsk delete result.searchParameters delete result.credits + delete result.relatedSearches return { content: [], diff --git a/examples/search/wrangler.jsonc b/examples/search/wrangler.jsonc index 4c1ec944..a6e24ca7 100644 --- a/examples/search/wrangler.jsonc +++ b/examples/search/wrangler.jsonc @@ -8,5 +8,9 @@ "compatibility_date": "2025-05-25", "compatibility_flags": ["nodejs_compat"], "placement": { "mode": "smart" }, - "upload_source_maps": true + "upload_source_maps": true, + "observability": { + "enabled": true, + "head_sampling_rate": 1 + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d812bf76..917e977d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -834,9 +834,6 @@ importers: '@agentic/platform': specifier: workspace:* version: link:../../packages/platform - '@agentic/platform-core': - specifier: workspace:* - version: link:../../packages/core '@agentic/serper': specifier: 'catalog:' version: 7.6.7(zod@3.25.67) diff --git a/readme.md b/readme.md index 28db5ee7..7df7c121 100644 --- a/readme.md +++ b/readme.md @@ -38,7 +38,7 @@ - merge with current agentic repo - publish packages to npm - api - - deploy to prod + - **deploy to prod** - database - consider using [neon serverless driver](https://orm.drizzle.team/docs/connect-neon) for production - can this also be used locally?