Skip to main content
If the service you want isn’t in the catalogue and has no MCP server of its own, write one. create-ragen-connector scaffolds it:
You get a working MCP server, the values to paste into Ragen’s catalogue, and — the part that is hard to discover otherwise — the three places Ragen’s client behaves differently from what the MCP specification alone would tell you.

What you get

Then:

Two ports, and both matter

A connector is two listeners: Hono on PORT for health and REST, and FastMCP on PORT + 1000 for /mcp. They cannot share one. A green /health proves nothing about the second. If you publish only the first from a container, the service looks healthy and has no tools — which at the client reads as a Ragen bug rather than a deployment one.

Where it runs

The CLI produces one of two shapes, chosen by where you run it:
  • Standalone — anywhere. Its own package.json, git repository, Dockerfile and tests. This is what you want for your own connector.
  • Workspace — inside a ragen-connectors checkout, as services/<slug>, joining the existing build and port table.
Force either with --target=standalone or --target=workspace.

Three things Ragen does that the MCP spec does not

Every generated connector encodes these, with a test beside each. They are the reason to scaffold rather than start from a bare MCP template.

customer_id arrives as a tool parameter

Ragen injects {orgId}:{userId}:{slug} into every tool call as a parameter, and strips it from the schema the model sees — so the model can neither read nor choose it. It is your multi-tenancy key: scope your data by it. There is an x-customer-id header too, but only for server_side connectors. An api_key_bearer connector gets Authorization and no customer header. Take the parameter, which is the channel Ragen guarantees on every auth type.

A tool returns an envelope and never throws

A thrown error reaches the model as an opaque protocol failure it cannot act on or explain to the user. Write the error in the caller’s terms — the model will repeat it.

Your session handler must refuse nothing

Ragen’s Test connection button opens an MCP session and lists your tool names with no headers at all — no customer id, no credential. That is the only check an operator gets that the address is right. A connector that demanded a header would fail at precisely the moment somebody is verifying it works. So accept the session, and enforce credentials per tool call, returning the {success: false} envelope. The generated auth.ts does this and explains the trade: your tool names become readable to anyone who can reach the port; your data does not.

Authentication

external_mcp (OAuth) is not scaffolded. FastMCP can serve the discovery endpoints Ragen looks for, but a generated authorization server nobody can run without their own provider and credentials would be boilerplate you cannot verify. Wire FastMCP’s oauth option by hand if you need it.

Flags

Every prompt has one, so this runs in CI. A malformed flag is refused, not ignored — --auth= with no value, or --yes=true, both stop the run. A typo that silently changes nothing is how a CI job scaffolds the wrong connector and reports success.

Connect it to Ragen

ragen-connector.json holds the values; a platform admin pastes them into MCP Catalogue. Full procedure, and the two things that catch people, in Adding a connector. The short version: the URL must end in /mcp, and localhost is refused by the address policy even with “allow a private address” ticked — use your LAN address while developing.

Before you call it done

Then start it and confirm both ports answer. The health check alone has never been evidence.
Replace the example tools with real ones, but keep the envelope contract and the customer_id parameter. Test the {success: false} branch too — an upstream is down or rate-limited far more often than a tool’s arguments are wrong, and that is the branch your users will actually meet.