turbomcp-openapi 3.2.0

OpenAPI to MCP conversion - expose REST APIs as MCP tools and resources
Documentation
# turbomcp-openapi

OpenAPI to MCP conversion for TurboMCP. Expose REST APIs as MCP tools and resources.

## Overview

This crate allows you to automatically convert an OpenAPI 3.0.x specification (via the `openapiv3` crate) into MCP (Model Context Protocol) tools and resources. This enables AI agents to interact with REST APIs without writing custom handlers.

**Default mapping:**
- `GET` endpoints → MCP Resources (readable content)
- `POST`, `PUT`, `PATCH`, `DELETE` endpoints → MCP Tools (callable operations)

## Quick Start

```rust
use std::path::Path;
use std::time::Duration;
use turbomcp_openapi::{OpenApiHandler, OpenApiProvider};
use turbomcp_server::{ServerBuilder, Transport};

// Load from URL
let provider = OpenApiProvider::from_url("https://api.example.com/openapi.json")
    .await?
    .with_base_url("https://api.example.com")?
    .with_timeout(Duration::from_secs(30));  // Optional, 30s default

// Or load from file
let provider = OpenApiProvider::from_file(Path::new("openapi.yaml"))?
    .with_base_url("https://api.example.com")?;

// Or load from string
let provider = OpenApiProvider::from_string(spec_json)?
    .with_base_url("https://api.example.com")?;

// Convert to an McpHandler and serve it with any transport
let handler: OpenApiHandler = provider.into_handler();
ServerBuilder::new(handler)
    .transport(Transport::stdio())
    .serve()
    .await?;
```

## Security Features

### SSRF Protection

The provider includes built-in Server-Side Request Forgery (SSRF) protection that blocks requests to:

- **Localhost/loopback**: `127.0.0.0/8`, `::1`, `localhost`
- **Private networks**: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`
- **Cloud metadata endpoints**: `169.254.169.254` and `169.254.0.0/16`
- **Link-local addresses**: `fe80::/10`
- **Other reserved ranges**: Multicast, broadcast, etc.

This prevents malicious API specs from making requests to internal infrastructure.

### Request Timeouts

All HTTP requests have a configurable timeout (default: 30 seconds) to prevent:
- Slowloris attacks
- Indefinite hangs on unresponsive servers
- Resource exhaustion

```rust
use std::time::Duration;

let provider = OpenApiProvider::from_string(spec)?
    .with_base_url("https://api.example.com")?
    .with_timeout(Duration::from_secs(10));  // 10 second timeout

// Check current timeout
println!("Timeout: {:?}", provider.timeout());
```

## Custom Route Mapping

You can customize how OpenAPI operations map to MCP types:

```rust
use turbomcp_openapi::{OpenApiProvider, RouteMapping, McpType};

let mapping = RouteMapping::new()
    // Default: GET -> Resource
    .map_method("GET", McpType::Resource)
    // Custom: All /admin/* paths are skipped
    .map_pattern(r"/admin/.*", McpType::Skip)?
    // Custom: Force specific paths to be tools
    .map_rule(["GET"], r"/api/search.*", McpType::Tool, 10)?;

let provider = OpenApiProvider::from_string(spec)?
    .with_route_mapping(mapping)
    .with_base_url("https://api.example.com")?;
```

## Route Mapping Rules

Rules are evaluated in priority order (highest first):

```rust
use turbomcp_openapi::{RouteRule, McpType};

// Create a rule matching POST/PUT to /users/* paths
let rule = RouteRule::new(McpType::Tool)
    .methods(["POST", "PUT"])
    .pattern(r"/users/\d+")?
    .priority(100);  // Higher priority = checked first
```

### McpType Variants

- `McpType::Tool` - Expose as MCP tool (callable operation)
- `McpType::Resource` - Expose as MCP resource (readable content)
- `McpType::Skip` - Don't expose via MCP

## Features

- **OpenAPI 3.0.x Support** - Parse both JSON and YAML specifications (via `openapiv3`)
- **Multiple Loading Methods** - From URL, file path, or string
- **Configurable Mapping** - Customize how operations map to MCP types
- **Regex Pattern Matching** - Route rules support regex path patterns
- **Parameter Handling** - Path, query, header, and cookie parameters
- **Request Body Support** - JSON request bodies converted to tool inputs
- **HTTP Client Integration** - Built-in reqwest client for API calls
- **Custom Client Support** - Provide your own configured reqwest::Client
- **SSRF Protection** - Built-in protection against server-side request forgery
- **Request Timeouts** - Configurable timeouts (default: 30 seconds)

## API Reference

### OpenApiProvider

The main entry point for loading and configuring OpenAPI specs:

```rust
impl OpenApiProvider {
    // Loading methods
    pub fn from_spec(spec: OpenAPI) -> Self;
    pub fn from_string(content: &str) -> Result<Self>;
    pub fn from_file(path: &Path) -> Result<Self>;
    pub async fn from_url(url: &str) -> Result<Self>;

    // Configuration
    pub fn with_base_url(self, base_url: &str) -> Result<Self>;
    pub fn with_route_mapping(self, mapping: RouteMapping) -> Self;
    pub fn with_client(self, client: reqwest::Client) -> Self;
    pub fn with_timeout(self, timeout: Duration) -> Self;

    // Inspection
    pub fn title(&self) -> &str;
    pub fn version(&self) -> &str;
    pub fn timeout(&self) -> Duration;
    pub fn operations(&self) -> &[ExtractedOperation];
    pub fn tools(&self) -> impl Iterator<Item = &ExtractedOperation>;
    pub fn resources(&self) -> impl Iterator<Item = &ExtractedOperation>;

    // Conversion
    pub fn into_handler(self) -> OpenApiHandler;
}
```

### OpenApiHandler

Implements `turbomcp_core::handler::McpHandler` for use with TurboMCP servers.
The handler exposes:

- `server_info()` — returns the spec's `info.title` / `info.version`
- `list_tools()` — one `Tool` per non-GET operation (subject to route mapping); `meta` includes the original `method`, `path`, and `operationId`
- `list_resources()` — one `Resource` per GET operation (subject to route mapping); `mime_type` is `application/json`
- `list_prompts()` — always empty (OpenAPI has no prompt concept)
- `call_tool(name, args, ctx)` — dispatches an HTTP request for the matching tool, with SSRF validation
- `read_resource(uri, ctx)` — issues the GET for the matching resource URI
- `get_prompt(...)` — always returns `prompt_not_found`

Tool names use `operation_id` when present, otherwise `{method}_{path}` with `/`
replaced by `_` and `{}` stripped. Resource URIs are
`openapi://{method}{path}` (e.g. `openapi://get/users/{id}`).

## Error Types

```rust
pub enum OpenApiError {
    FetchError(reqwest::Error),     // Failed to fetch spec from URL
    ParseError(String),              // Failed to parse spec (JSON/YAML)
    IoError(std::io::Error),        // Failed to read file
    InvalidUrl(url::ParseError),     // Invalid URL
    InvalidPattern(regex::Error),    // Invalid regex pattern
    ApiError(String),                // API call returned error
    MissingParameter(String),        // Required parameter missing
    InvalidParameter(String, String), // Invalid parameter value
    OperationNotFound(String),       // Operation not found
    NoBaseUrl,                       // Base URL not configured
    SsrfBlocked(String),            // SSRF protection blocked request
    Timeout(u64),                    // Request timed out
}
```

## Example

Given this OpenAPI spec:

```yaml
openapi: 3.0.0
info:
  title: Pet Store
  version: 1.0.0
paths:
  /pets:
    get:
      operationId: listPets
      summary: List all pets
    post:
      operationId: createPet
      summary: Create a pet
  /pets/{id}:
    get:
      operationId: getPet
      summary: Get a pet by ID
    delete:
      operationId: deletePet
      summary: Delete a pet
```

The handler exposes:

**Resources:**
- `openapi://get/pets` (listPets)
- `openapi://get/pets/{id}` (getPet)

**Tools:**
- `createPet` - Create a pet
- `deletePet` - Delete a pet

## Running the Example

```bash
cargo run -p turbomcp-openapi --example petstore
```

## Schema Handling Notes

- **`$ref` resolution**: references into `components.schemas` are resolved
  recursively and inlined into the emitted MCP tool/resource schemas, so
  consumers never see dangling pointers. Self-referential schemas are
  handled — the expander detects cycles and preserves the innermost `$ref`
  unchanged so output stays finite.
- **Schema composition**: `allOf`, `oneOf`, `anyOf`, `discriminator`, and
  `nullable` round-trip through `serde_json` as JSON Schema keywords, which
  downstream MCP clients that speak JSON Schema 2020-12 can consume directly.
- **Parameter `content`**: only `schema`-form parameters are extracted; the
  content-type variant form is not yet converted.
- **Security schemes**: declared schemes are recognized but not automatically
  applied to outgoing requests; callers supply auth headers manually.

## License

MIT