Skip to main content
Most services publish an OpenAPI document and no MCP server. openApiTools() turns that document into one typed tool per API operation, so an agent can call the API without a hand-written defineTool() per endpoint and without the raw http tool. Operations that change data ask for approval by default. openApiTools is exported from @lousho/build-ai-agent and @lousho/build-ai-agent/tools. It needs no extra package.

Quick start

openApiTools() is always async, because the document may be a URL. It returns an array of tools you pass to createAgent({ tools }) like any other. document can be a parsed OpenAPI 3.0 or 3.1 object, a JSON or YAML string, or an https URL (a string or a URL). Parsing happens once, inside openApiTools(); a tool’s execute only builds and sends a request.

What each tool looks like

Accept, Content-Type and Authorization header parameters are ignored, as the OpenAPI specification says; use bearerToken or headers for credentials.

Options

op is { name, operationId?, method, path, summary?, tags }.

Approval defaults

With approval: 'mutating', GET, HEAD and OPTIONS run, and every other method asks. Each tool also carries MCP-style annotations: readOnlyHint: true, destructiveHint: false on GET, HEAD and OPTIONS; readOnlyHint: false on the rest, with destructiveHint: true for DELETE. Anything that reads annotations treats these tools the way it treats MCP tools.

Values your application supplies

providedArguments fills parameters the model should not choose, such as a tenant id. A provided key is removed from the tool’s input schema (the model never sees it) and added to the request. The value can be a function, called per request with the operation and the tool call id.
The key is the parameter’s name (or body for the request body). A key that no selected operation has throws, so a typo does not silently expose the parameter to the model.

Security rules

  • The base URL is yours, not the model’s. The model chooses parameter and body values; it never chooses the host. The base URL must be https. Plain http is accepted only for localhost, 127.0.0.1 and [::1], and a URL with a user name or password is refused. The same rule applies to a document URL.
  • Credentials stay out of what the model and your logs see. Headers and tokens are never part of a tool’s input schema, its tool.start and tool.done events or the transcript; only the model’s own input and the response are. A header the model supplies for a declared header parameter never replaces headers or bearerToken.
  • Redirects stay on the base origin. Requests use manual redirects: at most 3 are followed, and only to the same origin as the base URL. A redirect to another origin is a tool error, so a token never leaves the configured origin.
  • Path values stay in their segment. Path parameters are percent-encoded (../admin cannot climb out of its segment), and a value of exactly . or .. is refused.
  • No private-address check. The tools call the base URL you configured. If that URL can resolve to an internal address that you do not control, put a proxy in front or pass your own fetch. The http tool’s address checks are for model-chosen URLs.

Large APIs

An API with hundreds of operations is hundreds of tools. Give the agent the ones it needs with include or exclude, or with a predicate on op.tags:

Limits

  • OpenAPI 3.0 and 3.1 only. A Swagger 2.0 document throws a ConfigurationError that asks you to convert it first.
  • Request bodies must be JSON (application/json or +json). An operation with only a multipart, form or XML body is left out; naming it in include throws.
  • Only the first servers entry is used, with variables at their defaults. Path-level and operation-level servers are ignored.
  • Every $ref must be local (#/components/...); a reference to another file or URL throws. Bundle the document first.
  • No OAuth flows. bearerToken and headers cover static and per-request credentials.
  • Response bodies are read as text, and responses are not streamed.