RavensPort

API to MCP new in 4.6.0

A bridge turns one API you already proxy into an MCP endpoint at /api-mcp/{slug}, described by a JSON manifest you write. It is the other half of a route: the route knows how to reach an upstream with a credential attached, and the manifest says which of that upstream's operations are worth offering to an agent and what to call them.

Point an agent straight at a bridge, or add it as a source on the MCP Funnel tab and pool it with everything else.

API to MCP tab

1. Pick a route and write a manifest#

On the API to MCP tab, press Load sample for a worked example using every part of the format, or Save sample… to edit one outside the app. Importing a file fills the editor rather than saving directly, so a manifest that fails validation can be fixed where it is. The preview below the editor lists every call the manifest would make before you save it.

Already have an OpenAPI 3.0/3.1 or Swagger 2.0 document for the API? Import OpenAPI… reads it — JSON or YAML — and shows you every operation it declares, so you pick which ones become tools before anything is converted.

Choosing which operations to import

Choosing matters because real documents are large and a manifest holds 64 tools: GitHub's own REST description declares 1239 operations, and Tailscale's 93. Converting the lot and keeping whatever fit would leave you with the first 64 in alphabetical order, which is never the handful you actually wanted. So:

The result is a manifest draft in the same editor, with path, query and header parameters mapped into inputSchema and JSON request bodies mapped into arguments. It is a draft, not a finished manifest: it does not know your route's prefix, so check every path against it before saving, and read the comment block at the top of what it produces — that is where it names anything it had to skip (credential-carrying headers, cookies, non-JSON bodies) or rename.

The command line converts a whole document instead, with no picking:

dotnet run --project tools/RavensPort.ManifestCheck -- --from-openapi spec.yaml --out my-api.json

Ready-made manifests live in templates/api-mcp/:

Manifest API Credential
google-drive-readonly.json Google Drive v3, read only OAuth, drive.readonly
google-routes.json Google Routes v2 Maps Platform API key
google-places.json Google Places v1 Maps Platform API key
google-weather.json Google Weather v1 Maps Platform API key
brightsky-dwd.json German weather and severe warnings none
mvg-munich.json Munich public transport none
transitous.json Worldwide public transport none
tailscale-readonly.json Tailscale API v2, read only Tailscale API access token
sample-task-tracker.json an invented API, every feature once —

SETUP.md lists the route each one needs: base URL, credential, and where the credential goes. AUTHORING.md is the format contract, and the thing to hand an agent you ask to write a manifest.

One manifest per base URL, because a bridge goes through exactly one route and a route has exactly one upstream — three Google APIs on three hosts are three bridges, even though one key opens all of them. Pool them behind a single endpoint with a funnel if an agent should see them as one server.

Check anything you or an agent produces without opening the app:

dotnet run --project tools/RavensPort.ManifestCheck -- my-api.json

It prints every call the manifest would make and calls the same validator the app does, so a file it passes is a file the app accepts.

{
  "version": 1,
  "instructions": "Search with list_tasks first; ids are opaque and only come from a listing.",
  "tools": [
    {
      "name": "list_tasks",
      "description": "Lists tasks matching a query.",
      "readOnly": true,
      "inputSchema": {
        "type": "object",
        "properties": { "query": { "type": "string" }, "limit": { "type": "integer" } },
        "required": ["query"]
      },
      "request": {
        "method": "GET",
        "path": "/tasks",
        "query": { "q": "{query}", "limit": "{limit}" },
        "headers": { "Accept": "application/json" }
      }
    }
  ]
}

Paths are relative to the route's prefix. Never put a token in a manifest — the route's credential is attached for you, one hop later, by the same transform that serves every other call.

2. Let the model choose between variants#

Several near-identical endpoints belong in one tool. Declare variants instead of request, and variantBy naming the argument the model sets to choose:

{
  "name": "list_by_state",
  "inputSchema": {
    "type": "object",
    "properties": { "state": { "type": "string", "enum": ["open", "done", "archived"] } },
    "required": ["state"]
  },
  "variantBy": "state",
  "variants": {
    "open":     { "description": "Live tasks",       "method": "GET", "path": "/tasks",         "query": { "status": "open" } },
    "done":     { "description": "Finished tasks",   "method": "GET", "path": "/tasks",         "query": { "status": "done" } },
    "archived": { "description": "Out of the list",  "method": "GET", "path": "/archive/tasks" }
  }
}

The enum on that argument is how the model learns its choices, so it must list exactly the variants that exist — RavensPort refuses a manifest where the two disagree in either direction. Each variant's description is appended to the tool description, because an enum of three strings tells a model the shape of the choice but nothing about the meaning. The selector never reaches the upstream; it is a routing decision, not a parameter.

3. Add guidance the agent can use#

Beyond tools, a manifest carries three kinds of guidance:

Field Becomes Seen by
instructions the server's instructions in the initialize result every MCP client
prompts MCP prompts, with arguments substituted into their text clients that list prompts
skills markdown documents served as resources at skill://{slug}/{name} clients that read resources

All three are answered from the manifest itself. A prompt or a skill never causes an HTTP call.

How arguments are used#

A placeholder is {name}, resolved against the arguments the model passed.

Where Missing argument Object or list Escaping
Path refused, naming the argument refused escaped per placeholder
Query value parameter omitted refused escaped
Header value header omitted refused control characters refused
Body template property omitted allowed when the value is exactly "{name}" none, it is JSON

Absent means omit, except in the path, where absent means refuse — a path with a hole in it is not a path. Declare what is required in the schema's required list, once, and the template never invents a null. bodyMode is none, template, or arguments (everything the path, query, headers and selector did not already carry).

Nothing validates arguments against the schema: it is advertisement to the model, and the only enforcement on the call path is that a placeholder the template needs is present and is a single value.

Where a manifest is stored#

Manifests live on local disk, not in the vault — %LocalAppData%\RavensPort\manifests\bridges\ (~/.local/share/RavensPort/manifests/bridges/ on Linux), one plain JSON file per bridge, unencrypted. A manifest is not secret (it never carries a credential — the route attaches one a hop later), so a vault held nothing here worth protecting, only a size ceiling nobody could see coming: at least one real backend (Proton Pass, measured directly) rejects an item's content past a few tens of KB, well under what a manifest is allowed to be. Moving it out of the vault removes that failure mode entirely.

Every manifest save, and every OpenAPI import, also drops a second, timestamped copy in %LocalAppData%\RavensPort\manifests\ (the parent of the bridges\ folder above) — a plain-text history you can reopen, diff, or recover an older version from. That one is never read back by the app; only the single canonical file per bridge is.

This means manifests no longer sync across machines that share a vault. Everything else — routes, credentials, funnels, a bridge's own name/slug/route/proxy key — still does, through the RavensPort Config note exactly as before. Only the manifest itself is local to the machine that saved it. Point the same vault at RavensPort on a second machine and a bridge shows up there with an empty manifest until you import or paste one in again.

Upgrading from a version that still stored manifests in the vault: the first load after upgrading reads each bridge's manifest out of its old vault item one last time, writes it to the new local file, and says so in the load notice. The stale vault item is then cleaned up automatically the next time that bridge is saved — same as any other item nothing refers to anymore.

Behaviour#

Limits#


Edit this page on the wiki Request support · Report vulnerabilities privately