An MCP server exposes capabilities — tools, resources, and prompts — that any compliant client or AI model can discover and use. In this lesson you build a simple calculator server and verify it works using the MCP Inspector.
If you build AI apps, you know that you can add tools and other resources to your LLM to make it more knowledgeable. When you place those tools on a server, any client — with or without an LLM — can use them.
Learning objectives
By the end of this lesson you will be able to:
- Set up an MCP development environment in your chosen language
- Build a basic MCP server that exposes tools and resources
- Inspect and test your server using the MCP Inspector
- Understand the three MCP server primitives: tools, resources, and prompts
What an MCP server can do
An MCP server can:
- Access local files and databases
- Connect to remote APIs
- Perform computations
- Integrate with other tools and services
- Expose user-facing prompts
Basic server structure
Every MCP server has the same three building blocks:
| Primitive | Purpose | Example |
|---|
| Tools | Functions the LLM can call | add(a, b) |
| Resources | Data the LLM can read | file://{path} |
| Prompts | Reusable message templates | review-code |
Exercise: Creating a server
Create the project
TypeScript
Python
.NET
Java
Rust
mkdir calculator-server
cd calculator-server
npm init -y
mkdir calculator-server
cd calculator-server
dotnet new console -n McpCalculatorServer
cd McpCalculatorServer
curl https://start.spring.io/starter.zip \
-d dependencies=web \
-d javaVersion=21 \
-d type=maven-project \
-d groupId=com.example \
-d artifactId=calculator-server \
-d name=McpServer \
-d packageName=com.microsoft.mcp.sample.server \
-o calculator-server.zip
unzip calculator-server.zip -d calculator-server
cd calculator-server
mkdir calculator-server
cd calculator-server
cargo init
Install dependencies
TypeScript
Python
.NET
Java
Rust
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
python -m venv venv
venv/bin/activate # or venv\Scripts\activate on Windows
pip install "mcp[cli]"
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting
./mvnw clean install -DskipTests
cargo add rmcp --features server,transport-io
cargo add serde
cargo add tokio --features rt-multi-thread
Write the server code
TypeScript
Python
.NET
Java
Rust
Create src/index.ts:import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "Calculator MCP Server",
version: "1.0.0"
});
// Add an addition tool
server.tool(
"add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// Add a dynamic greeting resource
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
contents: [{ uri: uri.href, text: `Hello, ${name}!` }]
})
);
// Add a code review prompt
server.prompt(
"review-code",
{ code: z.string() },
({ code }) => ({
messages: [{
role: "user",
content: { type: "text", text: `Please review this code:\n\n${code}` }
}]
})
);
const transport = new StdioServerTransport();
server.connect(transport);
Create server.py:from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Calculator MCP Server")
@mcp.tool()
def add(a: float, b: float) -> float:
"""Add two numbers together and return the result."""
return a + b
@mcp.tool()
def subtract(a: float, b: float) -> float:
"""Subtract b from a and return the result."""
return a - b
@mcp.tool()
def multiply(a: float, b: float) -> float:
"""Multiply two numbers together and return the result."""
return a * b
@mcp.tool()
def divide(a: float, b: float) -> float:
"""Divide a by b. Raises ValueError if b is zero."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
Replace Program.cs:using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using System.ComponentModel;
var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
[McpServerToolType]
public static class CalculatorTool
{
[McpServerTool, Description("Adds two numbers")]
public static string Add(int a, int b) => $"Sum {a + b}";
}
McpServerApplication.java:@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public ToolCallbackProvider calculatorTools(CalculatorService calculator) {
return MethodToolCallbackProvider.builder().toolObjects(calculator).build();
}
}
CalculatorService.java:@Service
public class CalculatorService {
@Tool(description = "Add two numbers together")
public String add(double a, double b) {
return String.format("%.2f + %.2f = %.2f", a, b, a + b);
}
@Tool(description = "Divide the first number by the second number")
public String divide(double a, double b) {
if (b == 0) return "Error: Cannot divide by zero";
return String.format("%.2f / %.2f = %.2f", a, b, a / b);
}
}
src/main.rs:use rmcp::{
ServerHandler, ServiceExt,
handler::server::{router::tool::ToolRouter, tool::Parameters},
model::{ServerCapabilities, ServerInfo},
schemars, tool, tool_handler, tool_router,
transport::stdio,
};
use std::error::Error;
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CalculatorRequest { pub a: f64, pub b: f64 }
#[derive(Debug, Clone)]
pub struct Calculator { tool_router: ToolRouter<Self> }
#[tool_router]
impl Calculator {
pub fn new() -> Self {
Self { tool_router: Self::tool_router() }
}
#[tool(description = "Adds a and b")]
async fn add(
&self,
Parameters(CalculatorRequest { a, b }): Parameters<CalculatorRequest>,
) -> String {
(a + b).to_string()
}
}
#[tool_handler]
impl ServerHandler for Calculator {
fn get_info(&self) -> ServerInfo {
ServerInfo {
instructions: Some("A simple calculator tool".into()),
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let service = Calculator::new().serve(stdio()).await?;
service.waiting().await?;
Ok(())
}
Build and run
TypeScript
Python
.NET
Java
Rust
Use mcp dev server.py to automatically launch the Inspector alongside your server.
./mvnw clean install -DskipTests
java -jar target/calculator-server-0.0.1-SNAPSHOT.jar
Run the MCP Inspector
TypeScript
Python
.NET
Java
Rust
npx @modelcontextprotocol/inspector node build/index.js
npx @modelcontextprotocol/inspector mcp run server.py
npx @modelcontextprotocol/inspector dotnet run
npx @modelcontextprotocol/inspector
# Select SSE transport, URL: http://localhost:8080/sse
npx @modelcontextprotocol/inspector --cli cargo run \
--method tools/call --tool-name add --tool-arg a=1 b=2
The Inspector opens a browser UI. Connect to your server, click Tools → listTools, select add, fill in two numbers, and run it. You should see the result returned immediately.
Common setup issues
| Issue | Solution |
|---|
| Connection refused | Check that the server is running and the port is correct |
| Tool execution errors | Review parameter validation and error handling |
| Authentication failures | Verify API keys and permissions |
| Schema validation errors | Ensure parameters match the defined schema |
| Server not starting | Check for port conflicts or missing dependencies |
Assignment
Create a simple MCP server with a tool of your choice:
- Implement the tool in your preferred language.
- Define input parameters and return values.
- Run the Inspector tool to ensure the server works as intended.
- Test the implementation with various inputs.
Key takeaways
- Setting up an MCP development environment is straightforward with language-specific SDKs.
- Building MCP servers involves creating and registering tools with clear schemas.
- Use the MCP Inspector to discover capabilities and test tool execution interactively.