MCP Server Development Guide: How to Build an MCP Server

By Atiq Md Rezaul Hoque Turjo · Published · Updated

MCP server development means building a small service that exposes tools, resources and prompts to AI applications through the Model Context Protocol. You choose one clear job, define a few well-described tools, pick a transport such as stdio, test with the MCP Inspector, then add authentication, logging and approval steps before connecting real systems.

Contents

What MCP is and why it exists

The Model Context Protocol, or MCP, is an open standard that describes how AI applications connect to outside tools and data. Before a standard like this, every link between an AI application and a business system needed its own custom code. If you had three AI applications and four systems, you could end up writing twelve separate integrations, each with its own quirks.

MCP replaces that pile of one-off work with one agreed way to describe and call capabilities. A system describes what it offers once. Any compatible AI application can then discover those capabilities and use them. People often compare it to a universal connector: you build the plug once, and many devices can use it.

MCP server development is the work of building that plug for your own system. You decide what the AI may see and do, write the code that carries out each action, and set the rules that keep it safe. The protocol handles the conversation between the two sides, so you can focus on the part that is unique to your business.

The standard keeps changing, so treat this guide as a map and not as a reference manual. The official specification and SDK documentation are the final word on exact names, versions and behavior. Check them before you write production code.

Architecture: host, client and server

Three roles appear in every MCP setup. Knowing which is which will save you confusion later.

The three roles in an MCP setup
RoleWhat it isExample
HostThe AI application the person usesA chat assistant, a coding tool or an agent you built
MCP clientA component inside the host that keeps one connection to one serverThe connector code the host creates for each server it uses
MCP serverThe program that exposes tools, resources and promptsThe service you are about to build

A host can run many clients at once, one per server. That is how a single assistant can read a calendar, search a document store and create a ticket in the same conversation. Each server stays small and focused, and the host combines them.

JSON-RPC underneath

MCP messages use JSON-RPC, a simple format for sending a request and getting a response. A client sends a request such as “list your tools” or “call this tool with these arguments”. The server replies with a result or an error. You rarely write these messages yourself, because the SDKs handle them. It still helps to know they exist when you read logs or debug a connection.

The connection lifecycle

  1. The client connects and the two sides agree on a protocol version and what each supports.
  2. The client asks the server what it offers: its tools, resources and prompts.
  3. The host passes those descriptions to the AI model.
  4. When the model decides to act, the client sends a request to the server.
  5. The server runs your code and returns the result, which goes back to the model.

Notice where the logic lives. The AI model chooses what to ask for, but your server decides what actually happens. That split is the reason the server is the right place for permissions and safety rules.

The three primitives: tools, resources and prompts

A server can offer three kinds of things. Most first projects use only tools, but it helps to know all three.

Tools

MCP tools are actions the model can ask the server to run. Examples are “look up an order”, “create a support ticket” or “search the knowledge base”. Each tool has a name, a description and a schema that defines its inputs. When the model decides to use one, that request is called a tool call. Tool calls are the part of MCP that does real work, so they are also where most of the risk sits.

Resources

Resources are pieces of information the client can read, such as a file, a database record or a report. They are meant for context. A resource is identified by a URI, and the application usually decides when to load it. Use a resource when the main job is to give the model something to read and no action is needed.

Prompts

Prompts are reusable instruction templates that a person can pick, often from a menu. A prompt might set up a standard code review request or a weekly report. They help people get consistent results without retyping long instructions.

Comparison of MCP primitives
PrimitiveWho usually triggers itBest forRisk level
ToolsThe model, during a taskActions and lookupsHigher, because tools can change things
ResourcesThe application or the userReading contextLower, but data can still be sensitive
PromptsThe userRepeatable workflowsLow, but prompts still need review

Transports: stdio and streamable HTTP

The transports carry JSON-RPC messages between the client and the server. The protocol defines more than one, and your choice affects how you deploy and secure the server.

Standard input output (stdio)

With the stdio transport, the client launches your server as a child process on the same machine. The two talk through standard input and output. This is the simplest option. There is no network port to open and no web server to run. It suits local tools, personal setups and development.

One rule matters a lot with stdio: your server must not print ordinary log text to standard output, because that channel carries the protocol messages. Send logs to standard error or to a file instead. Stray output on the wrong stream is one of the most common reasons a new server fails to connect.

Streamable HTTP

Streamable HTTP runs your server as a web service. Clients connect over the network, so one deployment can serve many people and many applications. This is the usual choice for shared, hosted or company-wide servers. It also means you must handle authentication, TLS, rate limits and uptime as you would for any web service.

Earlier versions of the specification described a different HTTP style based on server-sent events. Some older material still mentions it. Check the current specification for the transport details your SDK supports.

Comparison of stdio and streamable HTTP transports
AreastdioStreamable HTTP
Where it runsOn the user’s machine, started by the clientOn a server you host
Who can use itOne local clientMany remote clients
Setup effortLowHigher, like any web service
AuthenticationInherits the local user’s environmentYou must add it explicitly
Best forDevelopment, personal tools, local filesShared team tools and hosted products

A common path is to build and test over stdio, then add streamable HTTP when the server needs to be shared. The tool code stays the same. Only the way it is exposed changes.

Plan before you write code

Good MCP server development starts on paper. The code for a small server is short. The hard part is deciding what to expose.

Pick one job

Start with a single, concrete task, such as “let the support assistant check order status”. Avoid wrapping a whole system at once. A server that does one thing well is easier to secure, test and explain.

Design fewer, clearer tools

The model chooses tools by reading their names and descriptions. Vague or overlapping tools lead to wrong choices. Give each tool a plain verb-and-noun name, a description that says when to use it and when not to, and inputs with clear types. Return only the fields the task needs.

It also helps to think in terms of the user’s goal, not your internal API. If your system needs three calls to answer one common question, consider a single tool that does all three behind the scenes.

Decide read-only or write

Reading is lower risk than changing data. Start with read-only tools where you can. When you add actions that change things, such as sending a message or issuing a refund, plan the approval step at the same time.

Build an MCP server step by step

This section walks through a small server using the official TypeScript SDK, published as @modelcontextprotocol/sdk. The code is illustrative. SDK method names and import paths have changed between releases, so check the current SDK documentation for the exact API before you copy anything into a real project.

Step 1: Set up the project

Create a Node project, install the SDK and a schema library, and turn on TypeScript. The SDK uses a schema library to describe tool inputs.

# illustrative setup commands
mkdir order-status-server && cd order-status-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

Step 2: Create the server object

The server object holds a name, a version and the list of things you register on it. Pick a name that tells a human what the server does, because it may appear in client menus and logs.

// illustrative sketch: check current SDK docs for exact names
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: "order-status",
  version: "0.1.0",
});

Step 3: Register a tool

A tool needs a name, a description, an input schema and a handler function. The handler receives validated input and returns content for the model to read. This example is read-only and returns only a small set of fields.

// illustrative: a read-only tool with validated input
server.tool(
  "get_order_status",
  "Look up the delivery status of one order by its reference. Read-only.",
  { orderRef: z.string().min(3).max(40) },
  async ({ orderRef }) => {
    const order = await findOrder(orderRef); // your own data access
    if (!order) {
      return {
        content: [{ type: "text", text: "No order found for that reference." }],
        isError: true,
      };
    }
    return {
      content: [
        { type: "text", text: "Order " + order.ref + " status: " + order.status },
      ],
    };
  }
);

Three habits in this sketch are worth copying. The description says the tool is read-only. The input is validated before your code runs. The error message is clear enough for the model to explain the problem to the user. Here findOrder stands in for your own data access code.

Step 4: Add a resource or prompt if you need one

Not every server needs resources or prompts. Add them when they fit. A resource might expose a shipping policy document. A prompt might offer a ready-made “summarize this customer’s open orders” request. The SDK has matching registration methods for each, and the current documentation shows their exact signatures.

Step 5: Connect a transport and start

The last step connects the server to a transport. For local development, use stdio.

// illustrative: start the server over stdio
const transport = new StdioServerTransport();
await server.connect(transport);
// Log to stderr only. stdout carries protocol messages.
console.error("order-status server running on stdio");

Compile the project and you have a working local server. To serve many clients later, you would swap in the HTTP transport from the SDK and add authentication in front of it. Your tool code does not need to change.

What about other languages?

Official SDKs exist for several languages, and the concepts are the same in each. You register tools with names, descriptions and schemas, then connect a transport. Choose the language your team already runs in production. A server you can maintain is worth more than one written in the newest tool.

Testing and debugging

Test in layers. Each layer catches a different kind of problem.

Start with the MCP Inspector

The MCP Inspector is an official developer tool for exploring and testing a server without an AI model in the loop. You point it at your server, and it shows the tools, resources and prompts you registered. You can fill in arguments, call a tool by hand and read the raw response. The usual way to start it is with npx:

# illustrative: launch the Inspector against a local stdio server
npx @modelcontextprotocol/inspector node build/index.js

Check the Inspector’s own documentation for the current command, since options change. If your tools do not appear in the list, the problem is almost always in registration or startup, not in the model.

Unit-test the tool logic

Keep the real work in plain functions that you can test without any protocol code. Test normal results, empty results and errors. Then make the handler a thin layer that validates input and calls those functions.

Try bad input on purpose

Send missing fields, wrong types, very long strings and odd characters. Model-generated arguments can be wrong, and a malicious input can be worse. Your server should refuse bad input with a clear message and never crash or leak internal details.

Run real tasks with a real client

Connect an MCP client and give it realistic jobs. Watch which tools it picks, what arguments it sends and how it uses the results. This is where weak tool descriptions show up. If the model keeps choosing the wrong tool, rewrite the description before you change any code.

Debugging checklist

  • Server will not connect. Check that nothing writes to standard output except protocol messages, and that the start command works in a plain terminal.
  • Tools do not show up. Confirm they are registered before you connect the transport, and that the client was restarted after a config change.
  • Calls fail with validation errors. Compare the arguments the client sent with your schema. Logging the raw request helps.
  • Works in the Inspector but not in the client. Compare the working directory, environment variables and paths the client uses to start the process.
  • Random timeouts. Look for slow downstream calls. Add timeouts in your own code so the failure is clear.

How to add an MCP server to a client

Once the server works, you need to add MCP to the client you want to use. Each client has its own way, but the pattern is similar. Most let you edit a configuration file or use a settings screen to list servers.

For a stdio server, the entry usually names the command that starts it, any arguments and any environment variables. For a remote server, the entry gives the URL and any credentials or sign-in settings. The sketch below shows the general shape only. Field names differ between clients, so copy the format from your client’s documentation.

// illustrative shape only: real field names vary by client
{
  "mcpServers": {
    "order-status": {
      "command": "node",
      "args": ["/absolute/path/to/build/index.js"],
      "env": { "ORDERS_API_URL": "https://example.invalid/orders" }
    }
  }
}

A few tips make this step smoother. Use absolute paths, because the client may start your process from a different folder. Pass secrets through environment variables and keep them out of shared config files. Restart the client after changes. Then ask it to list its available tools to confirm the server loaded.

Many clients also ask the user to approve a tool call before it runs. Leave that on while you test. It lets you see exactly what the model is trying to do.

Security and permissions

An MCP server connects an AI model to real systems, so treat it as a security boundary. The model can be wrong, and the content it reads can be hostile. Your server is the last place where you can say no.

Least privilege

Give each tool the narrowest access it needs. A tool that reads order status should not hold credentials that can delete orders. Use a dedicated service account for the server, with only the permissions the exposed tools require. If one tool is compromised or misused, the damage stays small.

Authentication and authorization

A stdio server runs as the local user, so it inherits that user’s access. A remote server over HTTP must prove who is calling. The specification includes an authorization approach based on OAuth for HTTP servers, so check the current text and use your SDK’s support for it. Whatever method you choose, verify the caller on every request, and check what that caller is allowed to do. Never rely on the model to decide who is allowed.

Validate input and limit output

Check types, ranges and allowed values for every argument. Use allow lists where you can. Never build database queries or shell commands by pasting raw model text into them. On the way out, return only what the task needs, and keep sensitive fields such as payment details out of responses.

Guard against prompt injection

Content a model reads, such as a web page, an email or a document, can contain instructions meant to steer it. If your server returns text from untrusted sources, the model may follow instructions hidden in it. Do not let such content approve actions on its own. Keep a clear line between data the model reads and commands that carry authority.

Human approval for risky tool calls

Some tool calls should never run without a person saying yes. Examples are sending money, deleting records, messaging customers and changing permissions. Build the approval step into the workflow. The client may offer its own prompts, but you should not depend on that alone. For high-impact actions, enforce approval inside the server, for example by returning a pending request that a person confirms elsewhere.

Logging and audit

Record which tool was called, with what arguments, by which client or user, and what happened. Follow your privacy rules, and avoid writing secrets or personal data into logs. Good logs are how you debug, how you answer questions after an incident, and how you earn trust from the people who rely on the server.

Other controls worth having

  • Rate limits so a looping agent cannot flood a downstream system.
  • Timeouts on every outside call.
  • Separate environments so testing never touches production data.
  • A stop switch that lets you disable a tool quickly.
  • Dependency updates on a regular schedule, like any other service.

We make no claim that following these steps makes a system safe or compliant. Rules differ by industry and country, so involve your security and legal advisers early, especially for regulated data.

Deployment and monitoring

Choose where it runs

A stdio server can be shipped as a package or script that users install and the client launches. A streamable HTTP server is deployed like any web service: a container or a managed platform, behind TLS, with secrets in a proper store. Pick the model that matches who will use the server.

Version your tools

Clients and models come to depend on tool names and input shapes. Changing them silently can break agents that worked yesterday. Add new tools or optional inputs where you can. When you must make a breaking change, announce it, give the change a new version and keep the old one working for a while.

Monitor what matters

  • Availability. Is the server up and responding?
  • Errors. Which tools fail, and why?
  • Latency. Slow tools make agents slow and can cause timeouts.
  • Usage. Which tools are called, how often and by whom?
  • Unusual patterns. Sudden spikes or repeated failures can signal a bug or abuse.

Assign an owner

A server connected to live systems needs someone responsible for it. That person watches the logs, applies updates, and reviews changes when the connected system or the MCP specification moves on. Without an owner, servers quietly decay.

Common mistakes

  • Wrapping an entire API. Dozens of tools confuse the model. Expose the few that serve a real job.
  • Vague descriptions. The model picks tools from the text you write. Say what each tool does, what it needs and when not to use it.
  • Logging to standard output. On stdio this breaks the protocol stream.
  • Trusting the model’s arguments. Always validate, even when the schema looks strict.
  • Over-broad credentials. One powerful key shared by every tool turns a small bug into a big incident.
  • No approval for risky actions. Do not leave money, deletion or external messages to chance.
  • Returning too much data. Large or sensitive responses waste context and raise risk.
  • Testing only the happy path. Agents will send odd requests. Test failures as carefully as successes.
  • Hard-coded secrets. Use environment variables or a secret manager.
  • Skipping the spec. The protocol changes. Re-read the current documentation before each major release.

Build or use an existing server

You do not always have to build an MCP server. Many public servers exist for popular tools. The right choice depends on what you need.

When to use an existing MCP server and when to build your own
QuestionLean toward an existing serverLean toward building
Is the system common?Yes, and a maintained server existsNo, it is internal or custom
Do you need custom rules?No, defaults are fineYes, such as special approvals or filters
How sensitive is the data?Low, and you have reviewed the codeHigh, and you need full control
Who maintains it?A trusted outside teamYour team or a partner you choose

If you use someone else’s server, review its code and permissions as you would any software that touches your data. A server you did not write still runs with the access you give it. Prefer sources you can inspect, and pin versions so updates do not surprise you.

Also ask whether you need MCP at all. A single fixed integration for one application may be simpler as a direct API connection. MCP pays off when several AI applications or agents need the same access, or when you want one governed gateway that can grow.

How NextCTL builds MCP servers

NextCTL builds MCP servers as part of wider AI work. Our approach follows the same order as this guide. We start with one job and a short list of tools. We design permissions and approval steps before we write handlers. We test with the Inspector and with real client tasks, then add logging and monitoring before a server goes near live data.

You can read about the service on our MCP page. MCP servers often sit inside larger projects, such as AI integration services that connect assistants to your CRM, ERP or internal tools. They are also a building block for agentic AI systems and for AI agent development, where agents need controlled access to real systems.

We cannot promise specific business results, because they depend on your systems, your data and how the server is used. What we can do is scope the work clearly, build with sensible defaults for security and tell you plainly what the limits are.

Launch checklist

  1. One clear job and a short list of tools, each with a plain name and description.
  2. Read-only tools first, with approval planned for any action that changes data.
  3. Input validated on every tool and output limited to what the task needs.
  4. A transport chosen on purpose: stdio for local use, streamable HTTP for shared use.
  5. Least-privilege credentials stored outside the code.
  6. Authentication on any remote server, checked on every request.
  7. Logs that record tool calls without leaking secrets, written to the right stream.
  8. Tests: unit tests, the MCP Inspector, bad inputs and real client tasks.
  9. A version plan, monitoring and a named owner.
  10. A review of the current specification and SDK documentation before release.

Where to go next

Start small, keep the server narrow and let real use guide what you add. If you want help connecting AI to your own systems, see our MCP service and AI integration services, or learn how agents use these servers in agentic AI and AI agent development. More guides are on the NextCTL blog.

FAQ

Frequently Asked Questions

What is an MCP server?
An MCP server is a program that exposes tools, resources and prompts to AI applications using the Model Context Protocol. It describes what it offers in a standard format, receives requests from an MCP client, applies your rules and returns results.
What language should I use to build an MCP server?
Official SDKs exist for several languages, including TypeScript and Python. Pick the one your team already runs in production, then check the current SDK documentation for exact package names and API calls.
What is the difference between stdio and streamable HTTP?
With stdio, the client starts your server as a local process and talks to it over standard input and output. Streamable HTTP runs the server as a network service that many clients can reach, which suits shared and remote deployments.
How do I test an MCP server?
Use the MCP Inspector to list your tools and call them by hand, unit-test each tool, send bad inputs, then connect a real MCP client and run realistic tasks. Review the logs after every round.
Is an MCP server secure by default?
No. Security depends on how you build it. Apply least privilege, validate every input, authenticate remote callers, log tool calls and require human approval for risky actions.
How do I add an MCP server to a client?
Most clients let you add MCP servers through a configuration file or settings screen. For a stdio server you give the command and arguments to start it. For a remote server you give its URL and any credentials. Check your client's documentation for the exact format.
Should I build an MCP server or use an existing one?
Use an existing server when it already covers your tool and meets your security needs. Build your own when you need custom business logic, tighter permissions or access to an internal system that no public server covers.

See where your operations
are leaking revenue.

Tell us what you want to fix. We show where missed calls and slow follow up cost you most, with a written plan for what to fix first.

Or run a free tool first:Website AuditAI Readiness Scan