index

OpenAPI + HTTP tool call is enough. No need for MCP.

I don’t get the push to turn every API into an MCP.
With today’s reasoning models and tool-calling, AI Agents can already decide which API to call and what parameters to pass, given the context of the API specifications.

In most cases, we don’t need extra work to wrap APIs as MCP servers

HTTP Tool Call + openapi.yml is all you need

I’m going to demonstrate an example of using an HTTP tool call I built to an AI agent with OpenAPI specification provided in the system context.

I’m using Vercel AI SDK and I built a package of the HTTP tool, the context generator, and the view component.

Here are some of the code snippets. If you are not familiar with the code, please check out Tool Calling in Vercel AI SDK

The tool

export function createOpenAPITool() {
  const inputSchema = z.object({
    baseUrl: z.string().describe('Base URL for the API (extract from OpenAPI spec servers section or endpoint servers)'),
    endpoint: z.string().describe('The API endpoint path (e.g., /v1/forecast, /api/users)'),
    method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).describe('HTTP method from the OpenAPI spec'),
    queryParams: z.record(z.string(), z.unknown()).optional().describe('Query parameters as key-value pairs'),
    body: z.record(z.string(), z.unknown()).optional().describe('Request body for POST/PUT/PATCH requests'),
    headers: z.record(z.string(), z.string()).optional().describe('Additional headers (e.g., API keys, content-type)'),
  });

  type OpenAPIToolInput = {
    baseUrl: string;
    endpoint: string;
    method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
    queryParams?: Record<string, unknown>;
    body?: unknown;
    headers?: Record<string, string>;
  };

  return (tool as any)({
    description: `Execute API requests based on OpenAPI specifications. 
  This is a generic tool that can call any API endpoint defined in an OpenAPI spec.
  Extract the base URL, endpoint path, method, and parameters from the OpenAPI specification before using this tool.
  
  Important: The base URL must be extracted from the OpenAPI spec's 'servers' section or endpoint-specific servers.`,
    inputSchema: inputSchema as any,
    async *execute(input: any) {
      const { baseUrl, endpoint, method, queryParams, body, headers } = input as OpenAPIToolInput;
      yield { state: 'loading' as const, message: 'Preparing API request...' };

      try {
        // Construct URL with query parameters
        const url = new URL(endpoint, baseUrl);
        if (queryParams) {
          Object.entries(queryParams).forEach(([key, value]) => {
            if (value !== undefined && value !== null) {
              if (Array.isArray(value)) {
                url.searchParams.append(key, value.join(','));
              } else {
                url.searchParams.append(key, String(value));
              }
            }
          });
        }

        yield { state: 'loading' as const, message: `Executing ${method} ${url.toString()}...` };

        const defaultHeaders: Record<string, string> = {
          'Content-Type': 'application/json',
        };

        const options: RequestInit = {
          method: method as string,
          headers: {
            ...defaultHeaders,
            ...(headers ?? {}),
          },
        };

        if (body && ['POST', 'PUT', 'PATCH'].includes(method as string)) {
          options.body = JSON.stringify(body);
        }

        const response = await fetch(url.toString(), options);

        let data: unknown;
        const contentType = response.headers.get('content-type');
        if (contentType && contentType.includes('application/json')) {
          data = await response.json();
        } else {
          data = await response.text();
        }

        if (!response.ok) {
          yield {
            state: 'error' as const,
            statusCode: response.status,
            error: data,
            message: `API request failed with status ${response.status}`,
            url: url.toString(),
          };
          return;
        }

        yield {
          state: 'success' as const,
          statusCode: response.status,
          data,
          url: url.toString(),
          message: 'API request completed successfully',
        };
      } catch (error) {
        yield {
          state: 'error' as const,
          error: error instanceof Error ? error.message : 'Unknown error',
          message: 'Failed to execute API request',
        };
      }
    },
  });
}

The context generator

export function generateSystemPrompt(openapiSpec: string): string {
  return `You are a helpful AI assistant that can interact with APIs based on OpenAPI specifications. You have access to the following OpenAPI specification:

<openapi_specification>
${openapiSpec}
</openapi_specification>

## Your Capabilities:

1. **Answer questions** about the API by referencing the specification above
   - Explain available endpoints, parameters, and response formats
   - Describe what the API does and its capabilities
   - Clarify authentication requirements and usage patterns

2. **Execute API requests** using the 'openapi' tool when users want to retrieve actual data
   - Make real API calls based on user requests
   - Transform user queries into proper API calls

## How to use the 'openapi' tool:

The openapi tool is a generic tool that can execute any API request. You MUST extract all required information from the OpenAPI spec:

### Step 1: Extract the base URL
- Look in the OpenAPI spec for the 'servers' section at the root level (global servers)
- OR check for endpoint-specific servers under each path definition
- The base URL is typically found at:
  - Global: \`servers[0].url\`
  - Endpoint-specific: \`paths["/endpoint"].servers[0].url\`
- Example: "https://api.example.com"

### Step 2: Identify the endpoint and method
- Find the correct path from the \`paths\` section
- Identify the HTTP method (get, post, put, delete, patch)
- Example: path="/v1/users", method="GET"

### Step 3: Extract parameters and requirements
- Check the \`parameters\` section for the endpoint
- Identify which parameters are required (look for \`required: true\`)
- Note optional parameters and their types
- Check for request body schema if method is POST/PUT/PATCH

### Step 4: Handle authentication (if required)
- Look in \`components.securitySchemes\` or endpoint-level \`security\`
- Common types: apiKey (header/query), http (bearer/basic), oauth2
- Extract the required header name and format
- Example: { "Authorization": "Bearer token" } or { "X-API-Key": "key" }

### Step 5: Call the tool with complete information

Example structure:
\`\`\`
{
  baseUrl: "https://api.example.com",
  endpoint: "/v1/resource",
  method: "GET",
  queryParams: { 
    param1: "value1",
    param2: "value2"
  },
  headers: {  // Optional, for authentication
    "Authorization": "Bearer token"
  }
}
\`\`\`

## Example Workflow:

User asks: "Get me data from the API"

Your process:
1. Read the OpenAPI spec to understand what the API does
2. Identify the relevant endpoint and method
3. Extract the base URL from the servers section
4. Determine required parameters from user's request or reasonable defaults
5. Check if authentication is needed
6. Call the openapi tool with all extracted information
7. Present the results to the user

## Important Guidelines:

- **ALWAYS extract the base URL** from the OpenAPI spec - never assume or hardcode it
- **Read the spec carefully** for required vs optional parameters
- **Check for authentication** requirements in securitySchemes
- **Validate parameter types** against the spec (string, number, boolean, array, etc.)
- **Handle arrays properly** - some APIs expect comma-separated values
- **Cite specific sections** of the spec when answering questions
- **Be precise** - use exact parameter names and formats from the spec
- **If unclear**, ask the user for clarification rather than guessing

If the spec doesn't contain the requested information, clearly state that you don't know and explain what information is available.`;
}

Basically, given the OpenAPI spec and the conversation, the tool picks the API, fills in the parameters, and executes the HTTP request.

Demo: Asking Weather Agent with access to OpenMeteo API

You can see the demo in our example folder in Github. Here, we are going to provide an AI weather agent with OpenMeteo openapi.yml and ask it with “What’s the weather in Jakarta?”

AI Agent with HTTP Tool
AI Agent with HTTP Tool

As we can see, the AI agent can reason to decide which API endpoint to call and pass the correct parameters. Upon retrieving the API response, it will generate the text response to the user.

We need to focus more on API accessbility standard

Instead of building an MCP server for every API, API developers should focus on making their APIs more accessible to AI agents. For example, standardize a /openapi.yml at the site root (like /sitemap.xml for crawlers) so AI agents can easily discover the API spec and gain capabilities from it.

I believe this approach will accelerate adoption of more capable AI agents, rather than waiting for every API provider to build its own MCP server.

It turns out we’ve all been using MCP wrong

Cloudflare: “Code Mode: the better way to use MCP”