kortecxdocs
Tools

Authoring a Connector

Scaffold an MCP connector crate with one command, build it offline in fake mode, and check it against the same conformance gate the repository's CI runs.

A connector is a small separate program that teaches the agentic runtime a new tool — send a message, read a ticket, query an internal API. It runs as its own process. The runtime dials it, asks what tools it has, and calls them only when a run is allowed to.

If the connector you need does not exist yet, one command writes a working starter kit for it. The starter kit includes a fake mode: it answers with canned responses, so you can build and test the whole thing before you have a real account, a real key, or a network connection.

Before you start

Authoring a connector means writing Rust and running cargo. If you only want to connect a tool server that already exists, you do not need this page — see Connections.

This page assumes you are working inside a clone of the runtime repository. The scaffolded crate joins that Cargo workspace, its test harness is a path-dependency on a crate in crates/, and just test-connector is a recipe in that repository's justfile. Run every command below from the repository root.

Scaffold the crate

kx new connector runs offline. It does not contact a server.

kx new connector acme

That writes a self-contained crate at integrations/kx-connector-acme/. integrations is the default parent directory; --dir <parent> puts it somewhere else. Keep it inside the repository: the crate's test harness is a relative path dependency (../../crates/kx-extension-sdk), so a directory outside that tree will not compile. The name must be 1–48 characters of [a-z0-9-], with no leading or trailing dash — it becomes both the crate name suffix and the namespace your tools appear under.

Cargo.toml
main.rs
lib.rs
conformance.rs
README.md

What you get:

  • One starter tool, ping, that echoes a note back. Replace it with your real tools.
  • A fake mode. Set KX_ACME_FAKE and the connector answers from canned data — no network, no credential.
  • A credential read by name. The runtime injects the secret value into KX_ACME_CREDENTIAL; your code reads it there and authenticates inside its own process.
  • A conformance test that already passes, so you always have a green baseline to work back to.

The crate takes no dependency on the runtime crates it talks to — nothing from the gateway or its core is linked in. Its one internal dependency is a dev-dependency on the extension SDK, which exists only to run the test harness and is not compiled into the connector binary. That is why building or running the connector cannot disturb anything inside the runtime.

The scaffolder deliberately stops there. It does not edit your workspace Cargo.toml, does not register anything, and does not run the gate. The emitted README.md carries that checklist.

One line in the emitted README is out of date

The generated README.md ends with kx connections fire acme/ping --arg note=hi. That form does not parse. The commands on this page (--name, --tool, --args) are the ones the current CLI accepts.

What a connector must implement

A connector speaks JSON-RPC 2.0, one JSON request per line, over its standard input and output. (JSON-RPC is a plain request/response format: you get a line with a method and params, you write back a line with a result or an error.) The scaffold already implements all three methods.

MethodWhat you must return
initializea protocolVersion and a serverInfo block — the handshake
tools/listone entry per tool: name, description, and a JSON-Schema inputSchema
tools/callthe tool's result — or a JSON-RPC error if it failed

Two rules the scaffold follows, and your code must keep following:

  • Fail closed. An unknown method, an unknown tool, or arguments that do not parse must come back as a JSON-RPC error. Never invent a plausible success.
  • Never echo your environment. The injected credential must not appear in a reply, a log line, or an error message. If it does, it ends up in the run record.

Write your tool

Describe it in the catalog

src/lib.rs holds a CATALOG constant — the JSON array that tools/list returns. Add one object per tool, with a name, a one-line description, and an inputSchema describing the arguments. The description is what a model reads when deciding whether to call it, so write it for a reader, not for a compiler.

Decode the arguments

Add a small struct with #[derive(Deserialize)] for the tool's arguments, and a match arm in call that routes the tool's name to it. A decode failure should return error code -32602 (invalid params), which the scaffold already does for ping.

Do the work

Add a method on Client. Inside it, branch on self.fake first and return deterministic canned JSON; below that, make the real call using self.credential. Keeping the fake branch honest is what lets the rest of this page run without credentials.

Run the crate's tests

cargo test -p kx-connector-acme

This needs the crate to be a workspace member — add "integrations/kx-connector-acme" to the members list in your root Cargo.toml first. Two sets of tests run: the unit tests in src/lib.rs (the handshake, the tool listing, a successful call, and the three fail-closed paths), and tests/conformance.rs, which drives the whole connector through the gate described below with fake mode switched on.

Prove it with the conformance gate

The conformance harness is the part that matters. It does not read your code — it dials your connector the way the runtime dials it, registers the tools it finds, and then tries to make them misbehave.

Build the binary, then point the gate at it:

cargo build -p kx-connector-acme
just test-connector ./target/debug/kx-connector-acme

It prints one line per check, a JSON report, and exits non-zero if anything failed. (./target/debug/kx-connector-acme is where a default cargo build puts it; if you set CARGO_TARGET_DIR, use that path instead.)

The gate also takes a URL for a connector served over HTTP, or no argument at all to run against the bundled reference connector:

just test-connector https://mcp.example.com/rpc
just test-connector

The four checks

CheckWhat it asserts
Out-of-processevery tool your connector contributes is registered as an external tool, never as a built-in one
Warranta permission grant that names nothing is refused, a grant that names a different tool is refused, and a correct grant gets through
Secret-by-referencethe credential value appears in none of the sinks the harness can see — the request payload, the result handle, the stored result, the error text, the identifier of the unit of work
On / offwith the connector absent the tool does not fire at all, and registering it adds exactly its own tools and nothing else

Two details worth knowing:

  • The leak scanner self-tests before it runs. It plants a fake secret and confirms it catches it, so a broken scanner reports as a failure instead of quietly passing everything.
  • The warrant check fires your tool with placeholder arguments. If your tool rejects those arguments, that counts as a pass — a tool-level failure is not a permission failure, and the harness distinguishes the two. It also reports "unreachable" separately from "failed a check", so a connector that never started is never scored as compliant.

To exercise the secret-leak check for real, set the credential in your environment and tell the harness which variable it is:

KX_CONNECTOR_CRED_REF=KX_ACME_CREDENTIAL just test-connector ./target/debug/kx-connector-acme

Without that variable, the scanner still self-tests but has no secret to hunt for.

The same gate the repository's CI runs

Every in-tree connector carries this same tests/conformance.rs, and those tests run in the repository's own test sweep. The CI workflow additionally has a job that installs a pinned third-party tool server and dials it through the identical harness. Your connector is measured against the same checks, not a lighter copy of them.

Connect it to a running runtime

Once the gate is green, register the connector with a running server. Start the server first:

kx serve --dev-allow-local

Then store the credential by name and register the connector:

kx secrets set --name KX_ACME_CREDENTIAL --value <your-token>
kx connections add --name acme --command /absolute/path/to/target/debug/kx-connector-acme --credential-ref KX_ACME_CREDENTIAL

Use an absolute path for --command. A path with a separator in it is handed to the operating system exactly as written, and it is the server process that spawns the connector, so a relative path is read against the server's working directory rather than yours.

The secret value is write-only — it is sent once and no command ever reads it back. --credential-ref passes the name, never the value; the runtime resolves that name at the moment it spawns the connector and sets it as the environment variable of the same name in that child process. The value never travels through a request or lands in a record.

Check what was discovered, then call one tool by hand:

kx connections list
kx connections discover --name acme
kx connections fire --name acme --tool ping --args '{"note":"hello"}'

discover re-dials the connector and re-lists its tools. fire calls one tool live through the same broker an agent would use, and shows you the real result. It is a diagnostic, not a recorded run: it leaves no entry in the run history and is not replayable. Every discovered tool is namespaced acme/<tool> — the prefix is the connection name the server holds, not anything the connector claims — so two connectors that both expose a search tool do not collide.

Letting an agent use it

Registering a connector does not give an agent permission to fire its tools. kx connections fire is the checked way to call one from the OSS runtime.

For an autonomous agent loop to choose the tool itself, the operator of the server has to opt in: the auto-grant flag KX_FLAG_SERVE_AUTOGRANT is off by default, and turning it on (on a server that also has a model served) provisions a separate agent blueprint whose permission grant is rebuilt from the live tool registry when a run binds. Without that opt-in, kx agent run binds the standard agent blueprint, whose server-built grant covers only its own bundled tool — it will not reach acme/ping.

Either way the rule is the same: the model proposes a call, and the runtime decides whether the run's permission grant covers it.

Removing it

kx connections remove --name acme

That deregisters the tools the connector contributed. The crate stays on disk.