Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe
Front Page / AI Tools / Deep Dive

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

Deepak Bagada

CEO, SaaSNext

Aug 12, 2026 Published
|
Aug 12, 2026 Updated
|
16 Minutes Reading Time
Core Takeaways for Founders & Builders
  • 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_tool requires a contributor role, approve_tool and deny_tool require the admin group, allowlist_tool requires a tool-owner or platform-admin role, query_audit_log is 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 the prod environment.
  • Version pinning. Approve, then pin: agents resolve a server@version reference 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 audit table 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_score owned by the Risk team for the staging environment."
  • "Approve tool fraud_score as 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.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

Frequently Asked Questions
It is an enterprise directory inside Kong Konnect Catalog that registers, discovers, and governs MCP servers and tools, working as an extension of the existing API Catalog with ownership, blast radius, inherited policies, allowlisting, per-registry access control, and observability, so agents only reach approved resources.
A registry is a catalog that controls discovery and approval; a gateway enforces policy at runtime on traffic. They are complementary: the registry tells agents what is approved and the gateway enforces that approval, ideally sharing identity and policy.
Agents only ever query search_registry filtered by environment and state, and only approved or pinned tools are returned. Registering, approving by admin, and joining the agent's allowlist must all happen before a tool is discoverable.
Version pinning: approve once and lock agents onto a server@version reference instead of a floating endpoint. Any change to the upstream tool requires re-approval, preventing silent breaking changes from reaching agents.
Yes. The server is a standard stdio MCP process with a local SQLite store (upgrade to Postgres for HA), so it can run behind the firewall with mTLS transport and OAuth 2.0 identity, never exposed to the public internet.
Deepak Bagada
Author Profile

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.

Related Intelligence Analysis

Briefing AI Tools

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...

Deepak Bagada Deepak Bagada
12m read
Breaking AI Tools

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...

Deepak Bagada Deepak Bagada
4m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc