An AI agent is only as useful as the things it can reach. Left to the default arrangement, every agent on every developer machine gets its own configuration file listing its own set of MCP servers, each with its own credentials pasted in. Nobody knows who can call what. Nobody can revoke anything centrally. Nobody can answer "which tools did that agent actually invoke last Tuesday?"

Tiknix treats MCP the same way it treats authentication or permissions: as a primitive the application owns. There is one endpoint — POST /mcp/message — and it is a gateway.

The shape

Claude Code (single config) ──▶ Tiknix Gateway ──▶ Backend MCP Servers
                                   │
                                   ├── Built-in Tiknix tools
                                   ├── Shopify MCP
                                   ├── GitHub MCP
                                   └── Custom MCP servers

An agent configures one endpoint and one API key. Behind it, the gateway:

  • aggregates tools from every backend the caller is allowed to reach,
  • routes each call to the right backend,
  • authenticates and authorizes per key,
  • and logs every call.

The value is the same as any gateway: one place where policy lives. Adding a backend does not touch a single developer's machine. Revoking a key kills access to everything behind it, immediately, from an admin screen.

Namespacing, so tools cannot collide

Every tool is exposed as server:tool:

tiknix:reuse_digest
shopify:get_products
github:list_repos

Two backends can both offer search and nothing breaks. More importantly, the tool name an agent sees carries its provenance — reading a call log, you know where each call went without cross-referencing anything.

Two layers of authentication, and why the first one looks alarming

This is the part that makes people nervous on first read, so the codebase documents it at length in the controller header. The route permission is:

mcp::message  = 101   (PUBLIC)
mcp::registry = 101   (PUBLIC)

Public. On the MCP endpoint. That looks like a serious mistake, and it is not — it is a deliberate two-layer design:

  • Layer 1 (route) makes the endpoint reachable. It has to be: an MCP client must connect before it can authenticate, and the standard MCP flow is connect → list tools → authenticate → call tools.
  • Layer 2 (controller) does the real work. initialize, tools/list, and ping are public — they are discovery, which is documentation, not execution. tools/call requires a valid API key.

The project's own standards put the test bluntly:

DON'T PANIC if you see: mcp::message at level 101, or tools/list returning data without auth. This is correct.
DO PANIC if you see: tools/call working without an API key. That is a bug.

That is a good pattern for any security decision that looks wrong at a glance: write down what is intentional, what would constitute a real breach, and where the boundary sits. Otherwise a well-meaning reviewer "fixes" it in six months and breaks discovery for every client — or worse, assumes the whole thing is fine because it looked deliberate.

It also carries an explicit maintenance rule: new methods added to the public list require review. The list of things reachable without a key is small, enumerated, and guarded.

API keys are scoped capabilities

The apikey table is where per-agent policy lives:

ColumnPurpose
member_idWho owns this key — every call has an accountable human
allowed_serversWhich backends this key may reach
scopesWhat it may do
expires_atTime-bounded access
is_activeOne-flag revocation
token_hash, key_classHash-stored keys; broker keys as a distinct class
last_used_at, last_used_ip, usage_countIs this key still in use, and from where?

Members manage their own keys at /apikeys. The important properties are ordinary credential hygiene applied to agents: keys belong to a person, reach only what they are permitted to reach, expire, and die on one flag flip.

last_used_at deserves a mention. The hardest part of credential management is not issuing keys — it is knowing which of the forty existing keys are still needed. A last-used timestamp turns that from an archaeology project into a query.

Everything is logged, at two levels of detail

mcpusage records the analytics view: key, member, server, tool, response status, duration, IP, timestamp. mcplog records the forensic view: method, arguments, request body, response body, success flag, HTTP code, duration, error, user agent.

This matters more for agents than for humans. When a person clicks a button, intent is obvious. When an agent makes forty tool calls in ten seconds, "what did it actually do, in what order, with what arguments?" is the only way to reconstruct a bad outcome. Without a log at that granularity you are guessing.

The split is sensible: the analytics table stays queryable and cheap; the verbose table carries the payloads.

The same gateway serves the broker

The MCP gateway is also how a provisioned instance reaches its own connected stores. The instance holds a broker key — a revocable, hash-stored capability scoped to one instance — and calls the gateway. The gateway holds the actual Stripe or Shopify credentials and makes the upstream call.

So the same endpoint that gives an agent its tools also enforces the credential boundary described in Third-Party Credentials Without Sprawl. One chokepoint, one auth model, one audit trail — for agent tool use and for third-party API access.

Built-in tools

Tiknix's own MCP server exposes the codebase introspection tools (reuse_digest, codebase_map, whatprovides, describe), the validators (validate_php, check_redbean, check_flightphp), and the full pipeline surface (pipeline_list, pipeline_set, pipeline_run, pipeline_components, and the rest).

Adding a tool is dropping a class in mcptools/ that extends BaseTool and declares a name, a description, and an input schema — the same auto-discovery approach as pipeline steps. The description field is the actual interface: it is what an agent reads to decide whether to call the tool, so it deserves as much care as a function signature.

Why "MCP as a primitive" is the right framing

The alternative — every agent configured individually against every service — has the same shape as every credential problem that predates it. It works at one developer and falls apart at five: no inventory, no revocation, no audit, no consistency, and secrets on laptops.

Making the gateway part of the application means agent access gets the same treatment as user access. Identity (which key), authorization (which backends, which scopes), audit (both log tables), and revocation (one flag) are the same four questions you already answer for humans — answered in the same system, by the same admin, with the same tools.

Honest notes
  • The gateway is a single point of failure. Everything routes through it. When it is down, every agent loses every tool. That is the cost of centralization, and it means the gateway needs the monitoring you would give a login service.
  • Public discovery is a real, if small, disclosure. tools/list without a key tells an unauthenticated caller what tools exist and what they take. That is intentional and standard for MCP — but do not put sensitive detail in tool names or descriptions.
  • Verbose logs contain whatever agents send. mcplog stores request and response bodies. Those can include customer data, tokens in arguments, and anything else that passed through. Set a retention policy and treat that table as sensitive.
  • Scope checks are best-effort. As documented in ApiAuthService, an empty or absent scope list allows everything. Keys without explicit scopes are broader than they look — set them deliberately.
  • Namespacing prevents collisions, not confusion. An agent facing sixty tools across six backends chooses worse than one facing eight. Curate what a key can see; more reach is not more capability.
  • A gateway does not make tool calls safe. It authenticates, authorizes, and records. Whether a given tool should be callable by a given agent at all is still a judgment you have to make.