assert
Execute HTTP requests and validate responses with assertions, receiving pass/fail results for each check.
Instructions
Ejecuta un request y valida la respuesta con assertions. Retorna resultado pass/fail por cada assertion.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| method | Yes | HTTP method | |
| url | Yes | URL del endpoint (soporta /relativa y {{variables}}) | |
| headers | No | Headers HTTP | |
| body | No | Body del request (JSON) | |
| query | No | Query parameters | |
| auth | No | Autenticación | |
| assertions | Yes | Lista de assertions a validar contra la respuesta |
Implementation Reference
- src/tools/assert.ts:128-201 (handler)The registerAssertTool function registers the 'assert' MCP tool. It takes a method, url, headers, body, query, auth, and an array of assertions. It executes the HTTP request, evaluates each assertion against the response, and returns a pass/fail summary.
export function registerAssertTool( server: McpServer, storage: Storage, cache: ResponseCache, ): void { server.tool( 'assert', 'Ejecuta un request y valida la respuesta con assertions. Retorna resultado pass/fail por cada assertion.', { method: HttpMethodSchema.describe('HTTP method'), url: z.string().describe('URL del endpoint (soporta /relativa y {{variables}})'), headers: z.record(z.string()).optional().describe('Headers HTTP'), body: z.any().optional().describe('Body del request (JSON)'), query: z.record(z.string()).optional().describe('Query parameters'), auth: AuthSchema.optional().describe('Autenticación'), assertions: z .array(AssertionSchema) .describe('Lista de assertions a validar contra la respuesta'), }, async (params) => { try { const variables = await storage.getActiveVariables() const resolvedUrl = resolveUrl(params.url, variables) const config: RequestConfig = { method: params.method, url: resolvedUrl, headers: params.headers, body: params.body, query: params.query, auth: params.auth, } const interpolated = interpolateRequest(config, variables) const response = await executeRequest(interpolated) const callId = makeCallId() await cache.save(callId, interpolated.method, interpolated.url, response) // Evaluate assertions const results = params.assertions.map((assertion) => { const result = evaluateAssertion(response, assertion) return { ...result, assertion } }) const passed = results.filter((r) => r.pass).length const failed = results.filter((r) => !r.pass).length const allPassed = failed === 0 const lines: string[] = [ `${allPassed ? '✅ PASS' : '❌ FAIL'} — ${passed}/${results.length} assertions passed`, `${params.method} ${params.url} → ${response.status} ${response.statusText} (${response.timing.total_ms}ms)`, `call_id: ${callId}`, '', ] for (const r of results) { const icon = r.pass ? '✅' : '❌' lines.push(`${icon} ${r.message}`) } return { content: [{ type: 'text' as const, text: lines.join('\n') }], isError: !allPassed, } } catch (error) { const message = error instanceof Error ? error.message : String(error) return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true, } } }, ) - src/tools/assert.ts:30-126 (helper)The evaluateAssertion function evaluates a single assertion against a response. It supports 10 operators: eq, neq, gt, gte, lt, lte, contains, not_contains, exists, and type.
function evaluateAssertion( response: RequestResponse, assertion: { path: string; operator: string; expected?: unknown }, ): { pass: boolean; message: string } { const actual = getByPath(response, assertion.path) switch (assertion.operator) { case 'eq': return { pass: actual === assertion.expected, message: actual === assertion.expected ? `${assertion.path} === ${JSON.stringify(assertion.expected)}` : `${assertion.path}: esperado ${JSON.stringify(assertion.expected)}, recibido ${JSON.stringify(actual)}`, } case 'neq': return { pass: actual !== assertion.expected, message: actual !== assertion.expected ? `${assertion.path} !== ${JSON.stringify(assertion.expected)}` : `${assertion.path}: no debería ser ${JSON.stringify(assertion.expected)}`, } case 'gt': return { pass: typeof actual === 'number' && actual > (assertion.expected as number), message: `${assertion.path}: ${actual} > ${assertion.expected} → ${typeof actual === 'number' && actual > (assertion.expected as number)}`, } case 'gte': return { pass: typeof actual === 'number' && actual >= (assertion.expected as number), message: `${assertion.path}: ${actual} >= ${assertion.expected} → ${typeof actual === 'number' && actual >= (assertion.expected as number)}`, } case 'lt': return { pass: typeof actual === 'number' && actual < (assertion.expected as number), message: `${assertion.path}: ${actual} < ${assertion.expected} → ${typeof actual === 'number' && actual < (assertion.expected as number)}`, } case 'lte': return { pass: typeof actual === 'number' && actual <= (assertion.expected as number), message: `${assertion.path}: ${actual} <= ${assertion.expected} → ${typeof actual === 'number' && actual <= (assertion.expected as number)}`, } case 'contains': { let pass = false if (typeof actual === 'string') { pass = actual.includes(String(assertion.expected)) } else if (Array.isArray(actual)) { pass = actual.includes(assertion.expected) } return { pass, message: pass ? `${assertion.path} contiene ${JSON.stringify(assertion.expected)}` : `${assertion.path}: no contiene ${JSON.stringify(assertion.expected)}`, } } case 'not_contains': { let pass = true if (typeof actual === 'string') { pass = !actual.includes(String(assertion.expected)) } else if (Array.isArray(actual)) { pass = !actual.includes(assertion.expected) } return { pass, message: pass ? `${assertion.path} no contiene ${JSON.stringify(assertion.expected)}` : `${assertion.path}: contiene ${JSON.stringify(assertion.expected)} (no debería)`, } } case 'exists': return { pass: actual !== undefined && actual !== null, message: actual !== undefined && actual !== null ? `${assertion.path} existe` : `${assertion.path}: no existe`, } case 'type': return { pass: typeof actual === assertion.expected, message: typeof actual === assertion.expected ? `${assertion.path} es tipo ${assertion.expected}` : `${assertion.path}: esperado tipo ${assertion.expected}, recibido ${typeof actual}`, } default: return { pass: false, message: `Operador desconocido: ${assertion.operator}` } } } - src/tools/assert.ts:13-25 (schema)The AssertionSchema defines the Zod schema for each assertion object: path (JSONPath string), operator (enum of 10 operators), and optional expected value.
const AssertionSchema = z.object({ path: z .string() .describe( 'JSONPath al valor a validar: "status", "body.data.id", "headers.content-type", "timing.total_ms"', ), operator: z .enum(['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'contains', 'not_contains', 'exists', 'type']) .describe( 'Operador: eq (igual), neq (no igual), gt/gte/lt/lte (numéricos), contains/not_contains (strings/arrays), exists (campo existe), type (typeof)', ), expected: z.any().optional().describe('Valor esperado (no necesario para "exists")'), }) - src/server.ts:66-66 (registration)The registration of the assert tool in the main server. registerAssertTool is called with server, storage, and responseCache.
registerAssertTool(server, storage, responseCache)