Build a Kong MCP Registry Server for Enterprise Tool Governance & Shadow AI Control
Kong launched MCP Registry in Konnect in February 2026 to register, discover, and govern MCP servers and tools so agents only reach approved resources. This guide builds a portable TypeScript FastMCP registry with register, search, approve, allowlist, and audit tools, plus RBAC, approval gates, version pinning, OAuth 2.0/mTLS, and Claude Desktop plus Cursor wiring.
Deepak Bagada
CEO, SaaSNext
- Kong announced MCP Registry in Kong Konnect in February 2026 as an enterprise directory to register, discover, and govern MCP servers and tools, compliant with the open MCP Registry Specification and the AI Alliance Interoperability Framework.
- A registry is a discovery/approval layer; a gateway enforces runtime policy — Kong shares identity between both so approving and enforcing become one decision.
- The TypeScript FastMCP ak-corp-registry server exposes register_tool, search_registry, approve_tool, allowlist_tool, and query_audit_log with environment-scoped discovery and a publish state machine (draft → pending → approved → pinned/denied → retired).
- Enterprise governance requires RBAC on logged-in roles, multi-approver gates for prod, version pinning against floating endpoints, per-environment allowlists, and mTLS plus OAuth 2.0 on the transport.
- An immutable audit log exported to the SIEM turns every agent-tool change into the evidence trail GDPR, HIPAA, and EU AI Act compliance require.
The Model Context Protocol made it trivially easy for agents to connect to tools — and trivially easy for those connections to escape governance. In February 2026 Kong answered the sprawl by announcing MCP Registry, an enterprise directory inside Kong Konnect's Catalog (available in technical preview) that registers, discovers, and governs MCP servers and tools so AI agents connect only to approved, trusted resources with full visibility. The numbers explain the urgency: roughly one in five organizations reported a breach caused by shadow AI, only about a third have policies to manage or even detect it, and firms with heavy shadow AI usage saw about $670,000 more in breach costs than low-usage peers. Kong built the registry as an extension of the existing API Catalog, compliant with both the AI Alliance Interoperability Framework (AAIF) and the open MCP Registry Specification, so MCP servers are governed in the same operational context as every other API dependency — with ownership, blast radius, inherited policies, and audit trails. This guide builds a production-grade TypeScript FastMCP registry server, ak-corp-registry, that implements the control loop: register, search, approve, allowlist, and audit MCP tools, wired into Claude Desktop and Cursor with RBAC, approval gates, version pinning, and OAuth 2.0 plus mTLS. For the broader map of agent endpoints and directories, bookmark the MCP directory.
Registry vs. gateway: what we are actually building
A registry is a catalog layer — the "service catalog for the agentic era." It stores metadata about MCP servers and their tools: capability descriptions, transports, auth requirements, ownership, and lifecycle state. A gateway, by contrast, enforces policy at runtime on the traffic to those servers. They complement each other: the registry tells agents what is approved, and the gateway enforces that approval when agents connect. Kong ships both, sharing identity and policy so that approving a server in the registry and enforcing it at the AI Gateway become a single decision. This build covers the registry side: a server agents can query at discovery time, and administrators can govern at publish time. It mirrors the pattern and tool surface enterprises get from Konnect, but it is portable to any FastMCP or TypeScript host.
The registry contract
Before writing code, define the state machine every MCP tool moves through:
| State | Meaning |
|---|---|
draft |
Metadata submitted, not reviewed |
pending_approval |
In review by a gate at approval level |
approved |
Discoverable by agents at its environment tier |
pinned |
Approved and locked to a server/tool version |
denied |
Rejected; returned by search_registry as blocked |
retired |
Removed from discovery but retained in audit log |
Agents see only approved and pinned entries. Administrators see everything, and every transition appends to an immutable audit log. Per-environment separation — production agents only discovering approved entries tagged prod — is a baseline requirement for enterprise registries, and we encode it in each tool's signature.
Step 1: Scaffold the TypeScript FastMCP server
npm init -y && npm install @modelcontextprotocol/sdk zod sqlite3
npx tsc --init
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import Database from "sqlite3";
import { randomUUID } from "crypto";
const db = new Database.Database("registry.db");
db.serialize(() => {
db.run(`CREATE TABLE IF NOT EXISTS tools (
id TEXT PRIMARY KEY, name TEXT, description TEXT, server TEXT,
version TEXT REFERENCES versions(id), category TEXT,
owner TEXT, environment TEXT, state TEXT,
json_schema TEXT, created_at INTEGER, approved_by TEXT)`,
);
db.run(`CREATE TABLE IF NOT EXISTS audit (
id TEXT PRIMARY KEY, ts INTEGER, actor TEXT, action TEXT,
tool_name TEXT, detail TEXT)`);
});
const server = new McpServer({
name: "ak-corp-registry",
version: "1.0.0",
});
interface ToolRecord {
name: string; description: string; server: string;
version: string; category: string; owner: string;
environment: "dev" | "staging" | "prod";
inputSchema: Record<string, unknown>;
}
Step 2: Register, search, approve, allowlist, audit — the five tools
server.registerTool("register_tool", {
description: "Register an MCP server tool with metadata into the registry as draft.",
inputSchema: {
type: "object",
properties: {
name: { type: "string" },
description: { type: "string" },
server: { type: "string", description: "MCP server name or endpoint" },
version: { type: "string", pattern: "^\d+\.\d+\.\d+$" },
category: { type: "string" },
owner: { type: "string", description: "Team or individual DRI email" },
environment: { type: "string", enum: ["dev", "staging", "prod"] },
inputSchema: { type: "object", description: "JSON Schema of the tool's arguments" }
},
required: ["name", "description", "server", "version", "owner", "environment"]
},
async handler(args) {
const id = randomUUID();
await db.run(`INSERT INTO tools VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`, [
id, args.name, args.description, args.server, args.version,
args.category ?? "uncategorized", args.owner, args.environment,
"draft", JSON.stringify(args.inputSchema ?? {}), Date.now(), null,
]);
return { id, state: "draft",
note: "Awaiting approval; not yet discoverable by agents." };
},
});
server.registerTool("search_registry", {
description: "Search approved and pinned tools an agent may call, filtered by environment.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Intent or keyword, e.g. 'customer 360'" },
environment: { type: "string", enum: ["dev", "staging", "prod"] },
category: { type: "string" }
},
required: ["query", "environment"]
},
async handler(args) {
const rows = await new Promise<any[]>((res, rej) =>
db.all(`SELECT name, description, server, version FROM tools
WHERE state IN ('approved','pinned') AND environment = ?
AND (name LIKE ? OR description LIKE ?)`,
[args.environment, `%${args.query}%`, `%${args.query}%`],
(err, r) => err ? rej(err) : res(r)));
return { tools: rows };
},
});
server.registerTool("approve_tool", {
description: "Approval-gate: promote a draft/pending tool to approved (admin only).",
inputSchema: {
type: "object",
properties: {
tool_id: { type: "string" },
actor: { type: "string", description: "Admin email from auth token" },
pin_version: { type: "boolean", default: true }
},
required: ["tool_id", "actor"]
},
async handler(args) {
// RBAC: caller role must be in admin group (checked by middleware)
await db.run(`UPDATE tools SET state='approved', approved_by=?
WHERE id=?`, [args.actor, args.tool_id]);
await log("approve", args.actor, args.tool_id);
return { tool_id: args.tool_id, state: "approved",
pinned: args.pin_version };
},
});
server.registerTool("allowlist_tool", {
description: "Join a tool to an agent or team allowlist, or check membership.",
inputSchema: {
type: "object",
properties: {
op: { type: "string", enum: ["add", "check"] },
tool_id: { type: "string" },
principal: { type: "string", description: "Agent name or team group" },
environment: { type: "string", enum: ["dev", "staging", "prod"] }
},
required: ["op", "tool_id", "principal", "environment"]
},
async handler(args) {
if (args.op === "add") {
await db.run(`INSERT OR REPLACE INTO allowlist (tool_id, principal, environment)
VALUES (?,?,?)`, [args.tool_id, args.principal, args.environment]);
return { allowed: true };
}
const row = await new Promise<any>((res, rej) =>
db.get(`SELECT 1 FROM allowlist WHERE tool_id=? AND principal=? AND environment=?`,
[args.tool_id, args.principal, args.environment], (e, r) => e ? rej(e) : res(r)));
return { allowed: !!row };
},
});
async function log(action: string, actor: string, toolName: string, detail = "") {
await db.run(`INSERT INTO audit VALUES (?,?,?,?,?,?)`,
[randomUUID(), Date.now(), actor, action, toolName, detail]);
}
server.registerTool("query_audit_log", {
description: "Return audit entries for a tool or actor, newest first.",
inputSchema: {
type: "object",
properties: {
tool_name: { type: "string" },
actor: { type: "string" },
limit: { type: "number", default: 50 }
},
required: []
},
async handler(args) {
const rows = await new Promise<any[]>((res, rej) =>
db.all(`SELECT ts, actor, action, tool_name, detail FROM audit
WHERE (? IS NULL OR tool_name=?) AND (? IS NULL OR actor=?)
ORDER BY ts DESC LIMIT ?`,
[args.tool_name ?? null, args.tool_name ?? null,
args.actor ?? null, args.actor ?? null, args.limit ?? 50],
(e, r) => e ? rej(e) : res(r)));
return { entries: rows };
},
});
const transport = new StdioServerTransport();
await server.connect(transport);
Step 3: inputSchema definitions published to agents
Every registry tool advertises its own JSON Schema; the two your agents hit most are:
{
"name": "search_registry",
"description": "Find approved, environment-scoped MCP tools by intent.",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"environment": {"type": "string", "enum": ["dev", "staging", "prod"]},
"category": {"type": "string"}
},
"required": ["query", "environment"]
}
}
{
"name": "allowlist_tool",
"description": "Add or check agent-to-tool allowlist membership.",
"inputSchema": {
"type": "object",
"properties": {
"op": {"type": "string", "enum": ["add", "check"]},
"tool_id": {"type": "string"},
"principal": {"type": "string"},
"environment": {"type": "string", "enum": ["dev", "staging", "prod"]}
},
"required": ["op", "tool_id", "principal", "environment"]
}
}
Step 4: Wire into Claude Desktop and Cursor
{
"mcpServers": {
"ak-corp-registry": {
"command": "node",
"args": ["dist/server.js"],
"env": {
"REGISTRY_JWT_ISSUER": "https://id.example.com/sso",
"REGISTRY_ADMIN_GROUP": "ai-platform-admins"
}
}
}
}
Cursor reads the same block from .cursor/mcp.json, and because the registry server exposes only catalog operations it is safe to grant read tools like search_registry to every agent while restricting register_tool and approve_tool behind authorization.
Enterprise governance: RBAC, approval gates, version pinning, OAuth 2.0 / mTLS
The registry only controls shadow AI if the surrounding policies are real. In production enforce all five levers:
- RBAC with identity. Terminate OAuth 2.0 (or OIDC) at the transport, map JWT claims to roles, and gate every mutation:
register_toolrequires a contributor role,approve_toolanddeny_toolrequire the admin group,allowlist_toolrequires a tool-owner or platform-admin role,query_audit_logis read-only for auditors and compliance. - Approval gates. Nobody's tool is discoverable by agents until a human approver sets
state=approved. Where regulation demands it, require two approvals (platform + DRI) before anything reaches theprodenvironment. - Version pinning. Approve, then pin: agents resolve a
server@versionreference from the registry instead of a floating endpoint. A pinned entry does not move when the upstream tool changes, and updates require re-approval, which is how you stop silent breaking changes from reaching agents mid-flight. - mTLS for transport. Run the registry over mTLS inside the service mesh so only identity-bearing agents and administrators can reach it at all. Optionally pair with Kong AI Gateway policies so even approved tools are rate-limited, audited, and guarded against prompt-injection.
- Immutable audit. Every register → approve → deny → allowlist → retire transition is appended to the
audittable with actor, timestamp, and detail. Export it to your SIEM; that is the evidence trail GDPR, HIPAA, and EU AI Act compliance officers will ask for.
These are the same controls your API platform already applies to human traffic — a registry simply scopes them to machine identities, which is the point of treating agents as first-class consumers. For operational guidance on layering approval loops and notification rails around such controls, see our workflows vault.
Testing the full control loop
With Claude Desktop restarted and the server connected:
- "What MCP tools are available in the production environment for customer data? Search the registry."
- "Register a tool called
fraud_scoreowned by the Risk team for the staging environment." - "Approve tool
fraud_scoreas admin and report whether it became discoverable." - "Show me the audit log for
fraud_score, including who approved it and when."
The first prompt exercises search_registry; the rest exercise the admin surface: register_tool, approve_tool, allowlist_tool, and query_audit_log. The registry correctly hides forever any tool someone tried to register but never got approved.
Two operational details separate a demo registry from a production one. The first is coverage of the full lifecycle: retirement. When a tool is denied or retired, stop returning it from search_registry, but never delete the audit rows — read-only history is what lets compliance reproduce exactly which version an agent called three quarters ago. The second is enrichment of search_registry results with governance metadata every agent will actually need at call time: the server reference, the pinned version, the transport (stdio vs streamable HTTP), and the authentication expectations of the underlying API. Kong's own collection on the subject circles the same two points — a rich catalog plus an immutable trail is what makes "approved" an engineering statement rather than a policy aspiration. Agencies and platform teams that systematize these catalogs end up publishing them as first-class internal products; for a worked example of turning such operational loops into a reusable service layer, browse the workflows library.
Frequently Asked Questions
What problem does an MCP registry actually solve? It replaces hardcoded, ungoverned agent-to-tool connections with a centralized catalog: agents discover only approved tools, admins control who publishes and who consumes, and every transition is audited — eliminating the configuration drift and shadow AI that come from point-to-point manual wiring.
How is this different from Kong MCP Registry or the official public registry? The community registry at registry.modelcontextprotocol.io is a public catalog of everything; Kong MCP Registry is an enterprise directory inside Konnect that adds RBAC, allowlisting, per-environment separation, version pinning, and gateway enforcement on top of the same open MCP Registry Specification. This guide builds a portable TypeScript FastMCP equivalent you can run anywhere.
Does a registry replace an API gateway for MCP security? No. A registry governs discovery and approval; a gateway enforces policy (auth, rate limits, logging, injection defense) at runtime. Enterprises that comply hardest use both and share identity between them.
Can the registry run on-premise or in an air-gapped environment? Yes. The server is a standard stdio MCP process with a local SQLite store (upgrade to Postgres for HA), so it can live entirely behind the firewall with mTLS and never reach the public internet.
Who needs admin approval before an agent can use a tool? By design, no tool is discoverable by any agent until a human approver transitions it to approved, optionally with a second approver for the prod environment. Pinning an approved version makes that decision durable against upstream changes.
Closing thoughts
Kong's MCP Registry points the way: the protocol succeeded, so governance has to. A registry makes discovery and approval the same mechanism — agents can only find what administrators have blessed, and auditors can always replay who did what. This TypeScript/FastMCP implementation gives you that loop in under two hundred lines: register, search, approve, allowlist, and audit, with environment scoping, version pinning, RBAC, OAuth 2.0, and mTLS around it. Ship it in front of Claude Desktop and Cursor, filter every tool your agents touch through it, and shadow AI stops being a security finding and becomes a closed loop you control. Follow registry spec updates and ecosystem moves on latest AI news, and add any governed tool you publish to the MCP directory for organizational discoverability.
Enjoyed this breakdown? Get our morning dispatch in your inbox.
Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.
Deepak Bagada
CEO, SaaSNext
Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.
Build 9 Multi-Agent Clinical Trial Protocol Generation Workflows in 2026
Next Story →Master 7 Autonomous AI Energy Grid Balancing Workflows in 2026
Related Intelligence Analysis
Vercel AI SDK Tool Calling React: 5 Steps (2026)
Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...
Fact-Density vs. Word Count: The New SEO for 2026
Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...
NVIDIA Audex vs Qwen3.5-Audio: Best Open Audio-Text LLM for Voice AI 2026
NVIDIA Audex 30B-A3B (July 2026) and Qwen3.5-35B-A3B are the two leading open audio-text LLMs. Audex uniquely handles both audio understanding and generation in a single model while preserving text intelligence. Qwen3.5-...