Building a custom MCP server for an internal API starts with choosing an SDK, typically the official Python or TypeScript SDK, and mapping each API operation to be exposed to a discrete tool with a clear name, description and JSON Schema for its inputs and outputs. The description text matters more than it looks, because the language model uses it to decide when to call the tool, so vague or overlapping descriptions cause misfires. Next comes authentication: the server should use a service account or, better, pass through the calling user's own token so downstream permissions are respected rather than granting the model a single all-access credential. For internal-only use a stdio transport running as a local process is often enough, while shared or production deployments need the Streamable HTTP transport behind a reverse proxy with TLS and OAuth. Before shipping, test with the MCP Inspector tool and a real client to confirm the model calls the right tool with valid arguments, then add logging and rate limits. Nanobase AI, a Silicon Valley enterprise AI engineering company, builds and hardens these servers end to end for internal APIs across SAP, Salesforce and custom systems.

Start from the operations, not the API surface

The most common mistake in a first MCP server is mirroring the internal API one-to-one, turning every REST endpoint into a tool regardless of whether a model should ever call it directly. A better starting point is a short list of the actual business questions and actions the assistant needs to support, written in plain language first: "check a customer's open balance," "look up shipment status by order number," "draft a reply to a support ticket." Each of those becomes one tool, often backed by more than one underlying API call internally, rather than exposing the raw endpoints and hoping the model composes them correctly.

A minimal tool definition

Most official MCP SDKs follow a similar pattern: a decorator or registration call wraps a function, and the function's type hints or an explicit schema become the tool's input contract. Conceptually, a tool for looking up order status looks like this:

@mcp.tool()
def get_order_status(order_id: str) -> str:
    """Look up the current status of a single order by its order ID."""
    order = internal_api.get_order(order_id, user_token=current_user_token())
    return f"Order {order_id} is currently: {order.status}"

The docstring is not a comment, it is the description the model reads to decide when to call this tool, so it deserves the same care as the code itself.

The build sequence in order

  1. Pick an SDK matching your team's primary language; the official Python and TypeScript SDKs cover most enterprise stacks, with community SDKs available for others.
  2. List the target operations in plain language before writing any tool code, and cut anything that is really an internal implementation detail rather than something a user would ask for.
  3. Define each tool's input schema deliberately, using specific field names and types rather than a single free-text "query" parameter that hides ambiguity.
  4. Wire authentication so the server executes with the calling user's own permissions, not a single shared service credential, wherever the underlying system supports it.
  5. Choose stdio for local, single-user testing and Streamable HTTP for anything a second person or application needs to reach.
  6. Test with the MCP Inspector and at least one real client before calling it done, watching specifically for the model choosing the wrong tool or filling arguments incorrectly.
  7. Add structured logging of every call and its arguments, plus basic rate limiting, before any production rollout.

Skipping straight to step six without doing steps two and three properly is the most common reason a first MCP server underperforms in testing.

Design pitfalls worth avoiding

PitfallWhy it hurtsBetter approach
One generic run_query toolImpossible to scope permissions or reason about riskMultiple narrow tools, one per business question
Vague descriptions ("gets data")Model calls the wrong tool or none at allSpecific, example-driven descriptions
Returning full raw API responsesLeaks fields the user should not see, wastes contextShape the return value to only what's needed
No input validation before executionA malformed or malicious argument reaches the backendValidate against the schema and business rules server-side

The common thread across every row is scope: a narrower tool is both safer and easier for the model to use correctly.

Frequently asked questions

Should the server call the internal API directly or through a middleware layer?

Either can work, but going through an existing API gateway or middleware layer is usually safer because it inherits whatever rate limiting, logging and access controls that layer already enforces for other consumers, rather than the MCP server becoming a second, inconsistent entry point into the backend system.

How many tools should one server expose?

There is no fixed number, but servers with more than roughly fifteen to twenty tools tend to make it harder for a model to pick the right one and harder for a human to audit. Splitting a large system into multiple focused servers, one per domain, usually beats one server with dozens of loosely related tools.

Do I need to handle streaming responses for long-running operations?

Only if a tool's underlying operation can take more than a few seconds, such as generating a report. For those cases, Streamable HTTP supports sending progress updates before the final result, which keeps the user experience responsive instead of appearing frozen.

How Nanobase AI helps

Nanobase AI, a Silicon Valley enterprise AI engineering company, builds custom MCP servers end to end for internal APIs across SAP, Salesforce and proprietary systems, handling the tool design, authentication and hardening work described above rather than leaving it to a first internal attempt. The full architectural background is covered in what-is-mcp-how-to-build-an-mcp-server, and vetting guidance for servers you did not build yourself is in mcp-registry-vet-third-party-servers.

Ready to discuss your project? Contact Nanobase AI or email hello@bumu.tech.