teachyou.ai academy
← All posts
MCP

MCP for Teams: Building an Internal Tool Marketplace

Ira Menon · May 28, 2026 · 15 min read

Every team is quietly rebuilding the same tools

Walk into any engineering org with more than three teams and you'll find the same story repeated with minor variations. The platform team has a script that queries the deployment system. The data team has a notebook function that pulls customer usage stats. The support team has a Slack bot that looks up account status in the billing database. Each of these was built by someone who got tired of doing something manually, and each one is locked inside a single repo, a single Slack workspace, or a single engineer's head.

Now that large language models are becoming the interface every team wants to put in front of these tools, the duplication problem gets worse, not better. Six teams independently wrap the same "get customer by ID" query as a function for their own chatbot or agent. Six slightly different implementations, six sets of bugs, six places to update when the schema changes. This is the exact problem package managers solved for code twenty years ago, and it's the problem Model Context Protocol (MCP) is positioned to solve for AI tool access today.

MCP gives you a standard way to expose a capability — a database query, an internal API, a deployment action — as a server that any compliant AI client can discover and call. That standardization is the precondition for something more interesting than "my team has an MCP server": an actual internal marketplace, where any engineer building an agent can browse available tools, see who owns them, understand their permissions, and wire them up in minutes instead of weeks. This article walks through what that marketplace looks like in practice, how to build the first version of it, and the governance decisions you'll need to make before it becomes load-bearing infrastructure.

What "marketplace" actually means here

It's worth being precise about scope, because "marketplace" can conjure images of a public app store with ratings and payments. Internally, an MCP tool marketplace is really three things bundled together:

  • A registry — a machine-readable list of every MCP server available inside the company, what tools each one exposes, and metadata about ownership, environment, and status.
  • A discovery surface — a way for a human or an agent to search that registry and find "the tool that does X" without asking around on Slack.
  • A governance layer — the access control, logging, and approval process that determines who can register a server, who can call it, and what happens when something goes wrong.

None of these require exotic infrastructure. A registry can start as a JSON file in a git repo. Discovery can start as a README with a table, then graduate to a small internal web app. Governance can start as "your manager approves it in a pull request" before it becomes an automated policy engine. The mistake most teams make is trying to build the polished, self-service version on day one. Start with the JSON file. The value of a marketplace comes from consistent conventions applied everywhere, not from a fancy UI in front of three servers.

The building block: what a registered tool looks like

Before you can catalog MCP servers, you need every server to describe itself the same way. MCP already gives you this via tool definitions — each tool a server exposes has a name, a description, and a JSON Schema for its inputs. The marketplace's job is to aggregate these descriptions across every server in the company and make them searchable.

Here's a minimal internal MCP server exposing one tool, written with the TypeScript SDK, that follows conventions your marketplace can rely on:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "billing-lookup",
  version: "1.2.0",
});

server.registerTool(
  "get_customer_invoice_status",
  {
    title: "Get Customer Invoice Status",
    description:
      "Returns the current invoice status (paid, overdue, disputed) " +
      "for a given customer ID. Owned by team: billing-platform. " +
      "Data source: billing-prod (read replica).",
    inputSchema: {
      customerId: z.string().describe("Internal customer UUID"),
    },
  },
  async ({ customerId }) => {
    const status = await lookupInvoiceStatus(customerId);
    return {
      content: [{ type: "text", text: JSON.stringify(status) }],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Notice the description isn't just for humans skimming code — it's the exact text an LLM client sees when deciding whether to call this tool, and it's also the text your marketplace's search index will crawl. Two conventions matter here:

  • Owner and data source embedded in the description. This is a cheap trick that pays off constantly. When an agent or a human is deciding whether to trust a tool's output, "owned by team: billing-platform, read replica" answers half the questions they'd otherwise have to ask in Slack.
  • Semantic versioning on the server itself. Marketplaces live or die on whether consumers can tell if a tool changed underneath them.

Building the registry

The registry is the spine of the marketplace. At minimum, it's a structured manifest that every team contributes to when they stand up a new MCP server. A lightweight approach that scales surprisingly far is a single YAML file per server, stored in a shared mcp-registry repo, validated in CI.

name: billing-lookup
owner_team: billing-platform
slack_channel: "#billing-platform"
repo: github.com/acme-corp/billing-lookup-mcp
transport: stdio
environments:
  - name: production
    endpoint: internal://billing-lookup-prod
    auth: service-account
  - name: staging
    endpoint: internal://billing-lookup-staging
    auth: service-account
tools:
  - name: get_customer_invoice_status
    read_only: true
    pii: true
    approval_required: false
  - name: issue_refund
    read_only: false
    pii: true
    approval_required: true
status: active
last_reviewed: 2026-05-10

A CI job on this repo can do real work with almost no effort: validate the YAML schema, ping each declared endpoint to confirm it's alive, flag any server that hasn't been reviewed in the last quarter, and fail the build if a tool marked read_only: false doesn't also have approval_required: true. This is the governance layer showing up as code review rather than a bureaucratic process, which is exactly where you want it in the early stages.

Once you have a few dozen entries, generate a searchable index from the manifests — even a static site built at CI time that lists every tool, grouped by owning team, with the read-only/write and PII flags visible, gets you 80% of the value of a dedicated discovery UI.

Discovery: making tools findable by humans and agents

A registry nobody looks at isn't a marketplace, it's an archive. Discovery needs to work for two very different consumers.

Humans browsing the catalog want search-by-keyword, filtering by team, and a clear sense of "is this thing still maintained." A static site generated from the registry, deployed internally, with a simple search box over tool names and descriptions, covers most of this. Don't underestimate how much value comes from just making the existing tools visible — most duplication happens because nobody knew the tool already existed, not because building it twice was cheaper.

Agents need something more dynamic: the ability to enumerate available MCP servers at runtime and decide which tools are relevant to the current task. This is where an aggregator pattern helps. Instead of every agent config listing out fifteen individual MCP server connections, point agents at a single internal gateway that itself speaks MCP and proxies to the registered servers underneath:

from mcp import ClientSession
from mcp.client.sse import sse_client

async def discover_tools(gateway_url: str):
    async with sse_client(gateway_url) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            for tool in tools.tools:
                print(f"{tool.name}: {tool.description}")

# Gateway aggregates billing-lookup, deploy-status,
# customer-search, and a dozen other internal servers
# behind one endpoint, so agent configs stay simple.

This gateway is also the natural place to enforce governance, which brings us to the harder part of the marketplace.

Governance: the part that determines if this survives contact with security

The reason internal tool marketplaces fail isn't usually technical — it's that nobody decided who's allowed to publish a tool that can issue refunds, delete records, or read PII, and under what conditions. Get ahead of this before your marketplace has real usage, because retrofitting access control onto tools people already depend on is painful.

A few decisions worth making explicitly, in order of how early you need them:

  • Read vs. write tools get different approval paths. A tool that only queries data can have a lightweight review — a peer approval on the registry PR is often enough. A tool that mutates state (issuing refunds, triggering deploys, modifying user accounts) should require sign-off from the owning team's lead and, for anything touching money or PII, a security review.
  • Every tool call should be attributable to a human, not just an agent. If your gateway proxies calls, log the requesting agent, the underlying user or service account, the tool name, and the arguments (redacting sensitive fields). When something goes wrong at 2am, "which agent called issue_refund and on whose behalf" needs to be a five-minute lookup, not a forensic investigation.
  • Scope credentials per tool, not per team. It's tempting to give a team's MCP server a broad service account "because it's easier." Resist this. A tool that only needs to read invoice status should hold a credential that can only read invoice status. This is the same least-privilege principle you already apply to service-to-service auth — MCP tools are just another caller.
  • Deprecation needs a real process. Tools get replaced, schemas change, owning teams reorg. Mark a registry entry status: deprecated with a sunset date well before you delete it, and have your gateway emit a warning (not yet an error) when a deprecated tool is called, so you can find every remaining consumer before you break them.

None of this needs to be built by a platform team from scratch. Most of it can start as documented convention plus code review discipline, with automation added only where the manual process is visibly failing.

Authentication and the gateway layer in practice

The governance principles above only matter if there's a consistent enforcement point, and that's the real job of the gateway. Rather than every consuming team re-implementing auth checks against every server they call, route calls through a layer that can uniformly answer three questions: who is calling, what are they allowed to call, and did this call happen.

A practical gateway sits between agents and the individual MCP servers, and it typically handles token exchange so that individual server owners never have to trust a raw agent-issued token directly:

from fastapi import FastAPI, Request, HTTPException
from mcp_gateway import ToolRegistry, AuditLog

app = FastAPI()
registry = ToolRegistry.load("registry.yaml")
audit = AuditLog()

@app.post("/tools/{tool_name}/call")
async def call_tool(tool_name: str, request: Request):
    caller = await authenticate(request)
    entry = registry.find_tool(tool_name)

    if entry is None:
        raise HTTPException(404, "tool not registered")

    if entry.approval_required and not caller.has_role(entry.approver_role):
        raise HTTPException(403, "missing approval role for write tool")

    if entry.pii and not caller.has_scope("pii:read"):
        raise HTTPException(403, "caller lacks pii access scope")

    payload = await request.json()
    result = await registry.dispatch(entry, payload, on_behalf_of=caller.id)

    audit.record(
        tool=tool_name,
        caller=caller.id,
        agent=caller.agent_name,
        args=redact(payload, entry.pii_fields),
    )
    return result

This is deliberately unglamorous. It doesn't need machine learning, it doesn't need a fancy policy DSL on day one — a handful of boolean checks against fields you already put in the registry manifest gets you most of the safety you need. The important property is that this logic lives in exactly one place, so when security asks "can any agent issue a refund without a human in the loop," the answer is a five-minute read of one file instead of an audit across fifteen repos.

As the marketplace grows, this is also the natural place to add rate limiting per team, circuit breakers for flaky downstream servers, and cost tracking if some tools call metered external APIs (an LLM-powered search tool, for instance). None of that is MCP-specific — it's the same reliability engineering you'd apply to any internal API gateway — but it matters more here because the caller is an autonomous agent that might retry aggressively or fan out calls in ways a human clicking a UI never would.

Versioning and change management

Tool marketplaces have a failure mode that plain API marketplaces don't: the "client" calling your tool is often an LLM reasoning from a natural-language description, not code compiled against a fixed interface. That makes contract changes riskier in a subtle way — a human developer reads a changelog; an agent just sees whatever schema and description are live at call time.

A few habits keep this manageable:

  • Never silently change a tool's input schema. If get_customer_invoice_status needs an additional required field, ship it as get_customer_invoice_status_v2 and mark the old one deprecated in the registry, rather than mutating the original tool in place. Agents built against the old description will otherwise start failing in ways that are hard to trace back to a schema change.
  • Treat description edits as seriously as code changes. Tightening a tool's description to stop an agent from misusing it is a real fix, not just documentation polish — track it in the same PR review process as the implementation.
  • Publish a changelog per server, not just per tool. When five tools live in one server, a consumer scanning the registry wants a single place to see "what changed in billing-lookup this quarter," not five scattered histories.

Where this fits with your broader agent strategy

You don't need executive buy-in to start this. The pattern that works in practice:

  1. Pick one team's existing internal tool — something already used informally, like a deployment status checker or a customer lookup script — and wrap it as a proper MCP server with the conventions above (owner in the description, YAML manifest, versioned).
  2. Get it working with one real agent or assistant that at least two other engineers use regularly, so there's a visible before/after.
  3. Write up the manifest schema and the "how to register a server" steps as a short internal doc, and open the registry repo to other teams.
  4. When the second and third teams register their own servers, that's your signal to build the aggregator gateway — before that, a shared config file pointing to two or three servers is enough.
  5. Only after five or six teams are participating does it make sense to invest in a dedicated discovery UI, automated policy checks in the gateway, or a formal approval workflow tool.

The ordering matters. Building governance infrastructure before anyone is using the marketplace produces a beautiful system nobody adopts, because the first team through the door has to pay all the setup cost with none of the network effect benefiting them yet. Let usage pull the infrastructure into existence.

Common failure modes to watch for

A few patterns show up repeatedly once teams start doing this for real, and it's worth naming them before you hit them.

  • The marketplace becomes a graveyard of stale servers. Without a review cadence, half the registry ends up pointing at endpoints nobody maintains. Bake a last_reviewed field into the manifest from day one and have CI flag anything older than a quarter.
  • Tool descriptions optimized for humans, not for the models calling them. A tool named getData with a description like "gets the data" is useless to an LLM trying to decide whether to call it. Since the description doubles as the marketplace's search text and the model's decision input, invest real effort here — describe what the tool does, what it returns, and when to use it versus a similarly named tool.
  • One mega-server instead of many focused ones. It's tempting to have a single team's MCP server grow to expose forty tools across five domains because it's convenient to deploy. This defeats ownership clarity and makes the registry's per-team filtering useless. Keep servers scoped to a coherent domain, even if that means more repos.
  • No offboarding path for tools tied to a person who left. If a manifest's owner_team field is actually "whoever Priya reported to," you'll find out the hard way when Priya leaves and the tool breaks silently. Require a team, not a person, as the owner field, enforced in CI schema validation.
  • Treating the gateway as a single point of failure without planning for it. Once agents route everything through one aggregator, that aggregator's uptime becomes everyone's uptime. Plan for graceful degradation — if the gateway is down, does the agent fail entirely, or can it fall back to direct connections for critical tools?

Where this fits with your broader agent strategy

An internal tool marketplace isn't a side project — it's the infrastructure that determines whether your organization's agents can actually do useful work across team boundaries, or whether each team's agent stays stuck re-deriving the same three API calls. The teams that get the most leverage from AI agents in the next few years won't be the ones with the cleverest prompts; they'll be the ones whose agents can reach the widest set of well-described, well-governed tools without an engineer hand-wiring a new integration every time.

Start small: one server, one manifest, one consuming agent. Get the conventions right before you scale the number of servers, because retrofitting descriptions and ownership onto fifty existing tools is far more painful than establishing the pattern with the first five. The protocol layer is already standardized — MCP took care of that. What's left is the organizational work of registries, discovery, and governance, and that work compounds: every tool you register properly makes the next team's onboarding faster, and every team that skips the shortcuts makes the whole marketplace more trustworthy for everyone building on top of it.

If you want to go deeper on the mechanics of actually writing these servers — transports, schema design, authentication patterns, and testing MCP tools before you register them — that's exactly the ground we cover in Building & Integrating MCP Servers, the hands-on course that takes you from a single local server to production-grade tools ready for a marketplace like the one described here.