invoke_function
Test deployed functions via HTTP by sending requests with custom methods, headers, and bodies to verify functionality without building a frontend.
Instructions
Invoke a deployed function via HTTP. Returns the function's response body and status code. Useful for testing functions without building a frontend.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | Yes | The project ID | |
| name | Yes | Function name to invoke | |
| method | No | HTTP method (default: POST) | |
| body | No | Request body (string or JSON object) | |
| headers | No | Additional headers to send |
Implementation Reference
- src/tools/invoke-function.ts:23-82 (handler)Main handler function that executes the invoke_function tool. Validates project exists, constructs HTTP request with headers and body, calls the deployed function via API, handles payment-required (402) errors, and returns formatted response with status code, duration, and response body.
export async function handleInvokeFunction(args: { project_id: string; name: string; method?: string; body?: string | Record<string, unknown>; headers?: Record<string, string>; }): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> { const project = getProject(args.project_id); if (!project) return projectNotFound(args.project_id); const method = args.method || "POST"; const requestHeaders: Record<string, string> = { apikey: project.service_key, ...(args.headers || {}), }; const startTime = Date.now(); const res = await apiRequest(`/functions/v1/${args.name}`, { method, headers: requestHeaders, body: method !== "GET" && method !== "HEAD" ? args.body : undefined, }); const durationMs = Date.now() - startTime; if (res.is402) { const body = res.body as Record<string, unknown>; return { content: [ { type: "text", text: `## Payment Required\n\nAPI call limit exceeded. Renew or upgrade your project.\n\n\`\`\`json\n${JSON.stringify(body, null, 2)}\n\`\`\``, }, ], }; } if (!res.ok) return formatApiError(res, "invoking function"); const bodyStr = typeof res.body === "string" ? res.body : JSON.stringify(res.body, null, 2); const lines = [ `## Function Response`, ``, `| Field | Value |`, `|-------|-------|`, `| status | ${res.status} |`, `| duration | ${durationMs}ms |`, ``, `**Response body:**`, `\`\`\`json`, bodyStr, `\`\`\``, ]; return { content: [{ type: "text", text: lines.join("\n") }] }; } - src/tools/invoke-function.ts:6-21 (schema)Input schema definition using Zod validation. Defines required parameters: project_id and name (function name), and optional parameters: method (HTTP method, defaults to POST), body (string or JSON object), and headers (additional HTTP headers).
export const invokeFunctionSchema = { project_id: z.string().describe("The project ID"), name: z.string().describe("Function name to invoke"), method: z .string() .optional() .describe("HTTP method (default: POST)"), body: z .union([z.string(), z.record(z.unknown())]) .optional() .describe("Request body (string or JSON object)"), headers: z .record(z.string()) .optional() .describe("Additional headers to send"), }; - src/index.ts:146-151 (registration)Tool registration with MCP server. Registers 'invoke_function' tool with its description, schema, and async handler wrapper that delegates to handleInvokeFunction.
server.tool( "invoke_function", "Invoke a deployed function via HTTP. Returns the function's response body and status code. Useful for testing functions without building a frontend.", invokeFunctionSchema, async (args) => handleInvokeFunction(args), ); - src/client.ts:18-63 (helper)API request utility function used by the handler. Makes HTTP requests to the API endpoint, handles network errors, parses JSON/text responses, and returns standardized ApiResponse object with ok status, HTTP status code, and body.
export async function apiRequest( path: string, opts: ApiRequestOptions = {}, ): Promise<ApiResponse> { const { method = "GET", headers = {}, body, rawBody } = opts; const url = `${getApiBase()}${path}`; const fetchHeaders: Record<string, string> = { ...headers }; let fetchBody: string | undefined; if (rawBody !== undefined) { fetchBody = rawBody; } else if (body !== undefined) { fetchHeaders["Content-Type"] = fetchHeaders["Content-Type"] || "application/json"; fetchBody = JSON.stringify(body); } let res: Response; try { res = await fetch(url, { method, headers: fetchHeaders, body: fetchBody, }); } catch (err) { return { ok: false, status: 0, body: { error: `Network error: ${(err as Error).message}` }, }; } let resBody: unknown; const contentType = res.headers.get("content-type") || ""; if (contentType.includes("application/json")) { resBody = await res.json(); } else { resBody = await res.text(); } if (res.status === 402) { return { ok: false, is402: true, status: 402, body: resBody }; } return { ok: res.ok, status: res.status, body: resBody }; } - src/errors.ts:18-102 (helper)Error formatting utilities used by the handler. formatApiError creates agent-friendly error messages with status codes, hints, and actionable next steps. projectNotFound returns consistent error when project ID is not in keystore.
export function formatApiError( res: { status: number; body: unknown }, context: string, ): ToolResult { const body = res.body && typeof res.body === "object" ? (res.body as Record<string, unknown>) : null; // Primary message — try message (PostgREST), then error, then fallback const primary = body ? (body.message as string) || (body.error as string) || "Unknown error" : typeof res.body === "string" ? (res.body as string) : "Unknown error"; const lines: string[] = [ `Error ${context}: ${primary} (HTTP ${res.status})`, ]; // Supplementary fields from the API response if (body) { if (body.hint) lines.push(`Hint: ${body.hint}`); if (body.retry_after) lines.push(`Retry after: ${body.retry_after} seconds`); if (body.expires_at) lines.push(`Expires: ${body.expires_at}`); if (body.renew_url) lines.push(`Renew URL: ${body.renew_url}`); if (body.usage) { const u = body.usage as Record<string, unknown>; const parts: string[] = []; if (u.api_calls !== undefined) parts.push(`API calls: ${u.api_calls}/${u.limit || "?"}`); if (u.storage_bytes !== undefined) parts.push( `Storage: ${u.storage_bytes}/${u.storage_limit || "?"} bytes`, ); if (parts.length > 0) lines.push(`Usage: ${parts.join(", ")}`); } } // Actionable guidance based on HTTP status switch (res.status) { case 401: lines.push( `\nNext step: Re-provision the project with \`provision_postgres_project\`, or check that your service key is correct.`, ); break; case 403: lines.push( `\nNext step: The project lease may have expired. Use \`get_usage\` to check status, or \`renew_project\` to extend the lease.`, ); break; case 404: lines.push( `\nNext step: Check that the resource name and project ID are correct.`, ); break; case 429: lines.push(`\nNext step: Rate limit hit. Wait and retry.`); break; default: if (res.status >= 500) { lines.push(`\nNext step: Server error. Try again in a moment.`); } } return { content: [{ type: "text", text: lines.join("\n") }], isError: true }; } /** * Consistent "project not found in key store" error. */ export function projectNotFound(projectId: string): ToolResult { return { content: [ { type: "text", text: `Error: Project \`${projectId}\` not found in key store. ` + `Use \`provision_postgres_project\` to create a project first.`, }, ], isError: true, }; }