Welcome to your first steps with the Model Context Protocol (MCP)!
Whether you're new to MCP or looking to deepen your understanding, this guide will walk you through the essential setup and development process.
You'll discover how MCP enables seamless integration between AI models and applications, and learn how to quickly get your environment ready for building and testing MCP-powered solutions.
> TLDR; If you build AI apps, you know that you can add tools and other resources to your LLM (large language model), to make the LLM more knowledgeable.
However if you place those tools and resources on a server, the app and the server capabilities can be used by any client with/without an LLM.
This lesson provides practical guidance on setting up MCP environments and building your first MCP applications.
You'll learn how to set up the necessary tools and frameworks, build basic MCP servers, create host applications, and test your implementations.
The Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to LLMs.
Think of MCP like a USB-C port for AI applications - it provides a standardized way to connect AI models to different data sources and tools.
By the end of this lesson, you will be able to:
Before you begin working with MCP, it's important to prepare your development environment and understand the basic workflow. This section will guide you through the initial setup steps to ensure a smooth start with MCP.
Before diving into MCP development, ensure you have:
An MCP server typically includes:
Here's a simplified example in TypeScript:
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "Demo",
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(
"file",
// The 'list' parameter controls how the resource lists available files. Setting it to undefined disables listing for this resource.
new ResourceTemplate("file://{path}", { list: undefined }),
async (uri, { path }) => ({
contents: [{
uri: uri.href,
text: `File, ${path}!`
}]
})
);
// Add a file resource that reads the file contents
server.resource(
"file",
new ResourceTemplate("file://{path}", { list: undefined }),
async (uri, { path }) => {
let text;
try {
text = await fs.readFile(path, "utf8");
} catch (err) {
text = `Error reading file: ${err.message}`;
}
return {
contents: [{
uri: uri.href,
text
}]
};
}
);
server.prompt(
"review-code",
{ code: z.string() },
({ code }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please review this code:\n\n${code}`
}
}]
})
);
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
In the preceding code we:
calculator) with a handler function.Before you begin testing your MCP server, it's important to understand the available tools and best practices for debugging.
Effective testing ensures your server behaves as expected and helps you quickly identify and resolve issues.
The following section outlines recommended approaches for validating your MCP implementation.
MCP provides tools to help you test and debug your servers:
The MCP Inspector is a visual testing tool that helps you:
1. Discover Server Capabilities: Automatically detect available resources, tools, and prompts
2. Test Tool Execution: Try different parameters and see responses in real-time
3. View Server Metadata: Examine server info, schemas, and configurations
# ex TypeScript, installing and running MCP Inspector
npx @modelcontextprotocol/inspector node build/index.js
When you run the above commands, the MCP Inspector will launch a local web interface in your browser.
You can expect to see a dashboard displaying your registered MCP servers, their available tools, resources, and prompts.
The interface allows you to interactively test tool execution, inspect server metadata, and view real-time responses, making it easier to validate and debug your MCP server implementations.
Here's a screenshot of what it can look like:
| Issue | Possible Solution |
|-------|-------------------|
| Connection refused | Check if server is running and 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 |
| CORS errors | Configure proper CORS headers for cross-origin requests |
| Authentication issues | Verify token validity and permissions |
For local development and testing, you can run MCP servers directly on your machine:
1. Start the server process: Run your MCP server application
2. Configure networking: Ensure the server is accessible on the expected port
3. Connect clients: Use local connection URLs like http://localhost:3000
# Example: Running a TypeScript MCP server locally
npm run start
# Server running at http://localhost:3000
We've covered Core concepts in a previous lesson, now it's time to put that knowledge to work.
Before we start writing code, let's just remind ourselves what a server can do:
An MCP server can for example:
Great, now that we know what we can do for it, let's start coding.
To create a server, you need to follow these steps:
# Create project directory and initialize npm project
mkdir calculator-server
cd calculator-server
npm init -y
# Create project dir
mkdir calculator-server
cd calculator-server
# Open the folder in Visual Studio Code - Skip this if you are using a different IDE
code .
dotnet new console -n McpCalculatorServer
cd McpCalculatorServer
For Java, create a Spring Boot project:
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
Extract the zip file:
unzip calculator-server.zip -d calculator-server
cd calculator-server
# optional remove the unused test
rm -rf src/test/java
Add the following complete configuration to your *pom.xml* file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot parent for dependency management -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath />
</parent>
<!-- Project coordinates -->
<groupId>com.example</groupId>
<artifactId>calculator-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Calculator Server</name>
<description>Basic calculator MCP service for beginners</description>
<!-- Properties -->
<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<!-- Spring AI BOM for version management -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Dependencies -->
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Build configuration -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>21</release>
</configuration>
</plugin>
</plugins>
</build>
<!-- Repositories for Spring AI snapshots -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>
mkdir calculator-server
cd calculator-server
cargo init
Now that you have your project created, let's add dependencies next:
# If not already installed, install TypeScript globally
npm install typescript -g
# Install the MCP SDK and Zod for schema validation
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
# Create a virtual env and install dependencies
python -m venv venv
venv\Scripts\activate
pip install "mcp[cli]"
cd calculator-server
./mvnw clean install -DskipTests
cargo add rmcp --features server,transport-io
cargo add serde
cargo add tokio --features rt-multi-thread
Open the *package.json* file and replace the content with the following to ensure you can build and run the server:
{
"name": "calculator-server",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "npm run build && node ./build/index.js",
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "A simple calculator server using Model Context Protocol",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.16.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/node": "^24.0.14",
"typescript": "^5.8.3"
}
}
Create a *tsconfig.json* with the following content:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Create a directory for your source code:
mkdir src
touch src/index.ts
Create a file *server.py*
touch server.py
Install the required NuGet packages:
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting
For Java Spring Boot projects, the project structure is created automatically.
For Rust, a *src/main.rs* file is created by default when you run cargo init. Open the file and delete the default code.
Create a file *index.ts* and add the following code:
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "Calculator MCP Server",
version: "1.0.0"
});
Now you have a server, but it doesn't do much, let' fix that.
# server.py
from mcp.server.fastmcp import FastMCP
# Create an MCP server
mcp = FastMCP("Demo")
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 =>
{
// Configure all logs to go to stderr
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
// add features
For Java, create the core server components. First, modify the main application class:
*src/main/java/com/microsoft/mcp/sample/server/McpServerApplication.java*:
package com.microsoft.mcp.sample.server;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.microsoft.mcp.sample.server.service.CalculatorService;
@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();
}
}
Create the calculator service *src/main/java/com/microsoft/mcp/sample/server/service/CalculatorService.java*:
package com.microsoft.mcp.sample.server.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
/**
* Service for basic calculator operations.
* This service provides simple calculator functionality through MCP.
*/
@Service
public class CalculatorService {
/**
* Add two numbers
* @param a The first number
* @param b The second number
* @return The sum of the two numbers
*/
@Tool(description = "Add two numbers together")
public String add(double a, double b) {
double result = a + b;
return formatResult(a, "+", b, result);
}
/**
* Subtract one number from another
* @param a The number to subtract from
* @param b The number to subtract
* @return The result of the subtraction
*/
@Tool(description = "Subtract the second number from the first number")
public String subtract(double a, double b) {
double result = a - b;
return formatResult(a, "-", b, result);
}
/**
* Multiply two numbers
* @param a The first number
* @param b The second number
* @return The product of the two numbers
*/
@Tool(description = "Multiply two numbers together")
public String multiply(double a, double b) {
double result = a * b;
return formatResult(a, "*", b, result);
}
/**
* Divide one number by another
* @param a The numerator
* @param b The denominator
* @return The result of the division
*/
@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";
}
double result = a / b;
return formatResult(a, "/", b, result);
}
/**
* Calculate the power of a number
* @param base The base number
* @param exponent The exponent
* @return The result of raising the base to the exponent
*/
@Tool(description = "Calculate the power of a number (base raised to an exponent)")
public String power(double base, double exponent) {
double result = Math.pow(base, exponent);
return formatResult(base, "^", exponent, result);
}
/**
* Calculate the square root of a number
* @param number The number to find the square root of
* @return The square root of the number
*/
@Tool(description = "Calculate the square root of a number")
public String squareRoot(double number) {
if (number < 0) {
return "Error: Cannot calculate square root of a negative number";
}
double result = Math.sqrt(number);
return String.format("β%.2f = %.2f", number, result);
}
/**
* Calculate the modulus (remainder) of division
* @param a The dividend
* @param b The divisor
* @return The remainder of the division
*/
@Tool(description = "Calculate the remainder when one number is divided by another")
public String modulus(double a, double b) {
if (b == 0) {
return "Error: Cannot divide by zero";
}
double result = a % b;
return formatResult(a, "%", b, result);
}
/**
* Calculate the absolute value of a number
* @param number The number to find the absolute value of
* @return The absolute value of the number
*/
@Tool(description = "Calculate the absolute value of a number")
public String absolute(double number) {
double result = Math.abs(number);
return String.format("|%.2f| = %.2f", number, result);
}
/**
* Get help about available calculator operations
* @return Information about available operations
*/
@Tool(description = "Get help about available calculator operations")
public String help() {
return "Basic Calculator MCP Service\n\n" +
"Available operations:\n" +
"1. add(a, b) - Adds two numbers\n" +
"2. subtract(a, b) - Subtracts the second number from the first\n" +
"3. multiply(a, b) - Multiplies two numbers\n" +
"4. divide(a, b) - Divides the first number by the second\n" +
"5. power(base, exponent) - Raises a number to a power\n" +
"6. squareRoot(number) - Calculates the square root\n" +
"7. modulus(a, b) - Calculates the remainder of division\n" +
"8. absolute(number) - Calculates the absolute value\n\n" +
"Example usage: add(5, 3) will return 5 + 3 = 8";
}
/**
* Format the result of a calculation
*/
private String formatResult(double a, String operator, double b, double result) {
return String.format("%.2f %s %.2f = %.2f", a, operator, b, result);
}
}
Optional components for a production-ready service:
Create a startup configuration *src/main/java/com/microsoft/mcp/sample/server/config/StartupConfig.java*:
package com.microsoft.mcp.sample.server.config;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class StartupConfig {
@Bean
public CommandLineRunner startupInfo() {
return args -> {
System.out.println("\n" + "=".repeat(60));
System.out.println("Calculator MCP Server is starting...");
System.out.println("SSE endpoint: http://localhost:8080/sse");
System.out.println("Health check: http://localhost:8080/actuator/health");
System.out.println("=".repeat(60) + "\n");
};
}
}
Create a health controller *src/main/java/com/microsoft/mcp/sample/server/controller/HealthController.java*:
package com.microsoft.mcp.sample.server.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HealthController {
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> healthCheck() {
Map<String, Object> response = new HashMap<>();
response.put("status", "UP");
response.put("timestamp", LocalDateTime.now().toString());
response.put("service", "Calculator MCP Server");
return ResponseEntity.ok(response);
}
}
Create an exception handler *src/main/java/com/microsoft/mcp/sample/server/exception/GlobalExceptionHandler.java*:
package com.microsoft.mcp.sample.server.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
ErrorResponse error = new ErrorResponse(
"Invalid_Input",
"Invalid input parameter: " + ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
public static class ErrorResponse {
private String code;
private String message;
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
}
// Getters
public String getCode() { return code; }
public String getMessage() { return message; }
}
}
Create a custom banner *src/main/resources/banner.txt*:
_____ _ _ _
/ ____| | | | | | |
| | __ _| | ___ _ _| | __ _| |_ ___ _ __
| | / _` | |/ __| | | | |/ _` | __/ _ \| '__|
| |___| (_| | | (__| |_| | | (_| | || (_) | |
\_____\__,_|_|\___|\__,_|_|\__,_|\__\___/|_|
Calculator MCP Server v1.0
Spring Boot MCP Application
Add the following code to the top of the *src/main.rs* file. This imports the necessary libraries and modules for your MCP server.
use rmcp::{
handler::server::{router::tool::ToolRouter, tool::Parameters},
model::{ServerCapabilities, ServerInfo},
schemars, tool, tool_handler, tool_router,
transport::stdio,
ServerHandler, ServiceExt,
};
use std::error::Error;
The calculator server will be a simple one that can add two numbers together. Let's create a struct to represent the calculator request.
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CalculatorRequest {
pub a: f64,
pub b: f64,
}
Next, create a struct to represent the calculator server. This struct will hold the tool router, which is used to register tools.
#[derive(Debug, Clone)]
pub struct Calculator {
tool_router: ToolRouter<Self>,
}
Now, we can implement the Calculator struct to create a new instance of the server and implement the server handler to provide server information.
#[tool_router]
impl Calculator {
pub fn new() -> Self {
Self {
tool_router: Self::tool_router(),
}
}
}
#[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()
}
}
}
Finally, we need to implement the main function to start the server.
This function will create an instance of the Calculator struct and serve it over standard input/output.
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let service = Calculator::new().serve(stdio()).await?;
service.waiting().await?;
Ok(())
}
The server is now set up to provide basic information about itself. Next, we will add a tool to perform addition.
Add a tool and a resource by adding the following code:
server.tool(
"add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
}]
})
);
Your tool takes parameters a and b and runs a function that produces a response on the form:
{
contents: [{
type: "text", content: "some content"
}]
}
Your resource is accessed through a string "greeting" and takes a parameter name and produces a similar response to the tool:
{
uri: "<href>",
text: "a text"
}
# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
In the preceding code we've:
add that takes parameters a and b, both integers.greeting that takes parameter name.Add this to your Program.cs file:
[McpServerToolType]
public static class CalculatorTool
{
[McpServerTool, Description("Adds two numbers")]
public static string Add(int a, int b) => $"Sum {a + b}";
}
The tools have already been created in the previous step.
Add a new tool inside the impl Calculator block:
#[tool(description = "Adds a and b")]
async fn add(
&self,
Parameters(CalculatorRequest { a, b }): Parameters<CalculatorRequest>,
) -> String {
(a + b).to_string()
}
Let's add the last code we need so the server can start:
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
Here's the full code:
// index.ts
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create an MCP server
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}!`
}]
})
);
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
server.connect(transport);
# server.py
from mcp.server.fastmcp import FastMCP
# Create an MCP server
mcp = FastMCP("Demo")
# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
# Main execution block - this is required to run the server
if __name__ == "__main__":
mcp.run()
Create a Program.cs file with the following content:
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 =>
{
// Configure all logs to go to stderr
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}";
}
Your complete main application class should look like this:
// McpServerApplication.java
package com.microsoft.mcp.sample.server;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.microsoft.mcp.sample.server.service.CalculatorService;
@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();
}
}
The final code for the Rust server should look like this:
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(())
}
Start the server with the following command:
npm run build
mcp run server.py
> To use MCP Inspector, use mcp dev server.py which automatically launches the Inspector and provides the required proxy session token.
If using mcp run server.py, youβll need to manually start the Inspector and configure the connection.
Make sure you're in your project directory:
cd McpCalculatorServer
dotnet run
./mvnw clean install -DskipTests
java -jar target/calculator-server-0.0.1-SNAPSHOT.jar
Run the following commands to format and run the server:
cargo fmt
cargo run
The inspector is a great tool that can start up your server and lets you interact with it so you can test that it works. Let's start it up:
> [!NOTE]
> it might look different in the "command" field as it contains the command for running a server with your specific runtime/
npx @modelcontextprotocol/inspector node build/index.js
or add it to your *package.json* like so: "inspector": "npx @modelcontextprotocol/inspector node build/index.js" and then run npm run inspector
Python wraps a Node.js tool called inspector. It's possible to call said tool like so:
mcp dev server.py
However, it doesn't implement all the methods available on the tool so you're recommended to run the Node.js tool directly like below:
npx @modelcontextprotocol/inspector mcp run server.py
If you're using a tool or IDE that allows you to configure commands and arguments for running scripts,
make sure to set python in the Command field and server.py as Arguments.
This ensures the script runs correctly.
Make sure you're in your project directory:
cd McpCalculatorServer
npx @modelcontextprotocol/inspector dotnet run
Ensure you calculator server is running
The run the inspector:
npx @modelcontextprotocol/inspector
In the inspector web interface:
1. Select "SSE" as the transport type
2. Set the URL to: http://localhost:8080/sse
3. Click "Connect"
You're now connected to the server
The Java server testing section is completed now
The next section it's about interacting with the server.
You should see the following user interface:
1. Connect to the server by selecting the Connect button
Once you connect to the server, you should now see the following:
1. Select "Tools" and "listTools", you should see "Add" show up, select "Add" and fill in the parameter values.
You should see the following response, i.e a result from "add" tool:
Congrats, you've managed to create and run your first server!
To run the Rust server with the MCP Inspector CLI, use the following command:
npx @modelcontextprotocol/inspector cargo run --cli --method tools/call --tool-name add --tool-arg a=1 b=2
MCP provides official SDKs for multiple languages:
Create a simple MCP server with a tool of your choice:
1. Implement the tool in your preferred language (.NET, Java, Python, TypeScript, or Rust).
2. Define input parameters and return values.
3. Run the inspector tool to ensure the server works as intended.
4. Test the implementation with various inputs.
Model Context Protocol (MCP)μ ν¨κ»νλ 첫 κ±Έμμ μ€μ κ²μ νμν©λλ€! MCPκ° μ²μμ΄λ μ΄ν΄λλ₯Ό λμ΄κ³ μ νλ , μ΄ κ°μ΄λλ νμ μ€μ λ° κ°λ° κ³Όμ μ μλ΄ν©λλ€. MCPκ° AI λͺ¨λΈκ³Ό μ ν리μΌμ΄μ κ°μ μνν ν΅ν©μ μ΄λ»κ² κ°λ₯νκ² νλμ§ μ΄ν΄λ³΄κ³ , MCP κΈ°λ° μ루μ κ΅¬μΆ λ° ν μ€νΈλ₯Ό μν νκ²½μ λΉ λ₯΄κ² μ€λΉνλ λ°©λ²μ λ°°μ°κ² λ©λλ€.
> TLDR; AI μ ν리μΌμ΄μ μ κ°λ°νλ€λ©΄ LLM(λν μΈμ΄ λͺ¨λΈ)μ λꡬμ κΈ°ν 리μμ€λ₯Ό μΆκ°νμ¬ LLMμ λ λλνκ² λ§λ€ μ μλ€λ κ²μ μμ€ κ²λλ€. νμ§λ§ λꡬμ 리μμ€λ₯Ό μλ²μ λ°°μΉνλ©΄ μ±κ³Ό μλ² κΈ°λ₯μ LLMμ΄ μλ μλ λͺ¨λ ν΄λΌμ΄μΈνΈκ° μ¬μ©ν μ μμ΅λλ€.
μ΄ μμ μ MCP νκ²½ μ€μ κ³Ό 첫 MCP μ ν리μΌμ΄μ ꡬμΆμ κ΄ν μ€μ©μ μΈ μλ΄λ₯Ό μ 곡ν©λλ€. νμν λꡬ λ° νλ μμν¬ μ€μ , κΈ°λ³Έ MCP μλ² κ΅¬μΆ, νΈμ€νΈ μ ν리μΌμ΄μ μμ±, ꡬν ν μ€νΈ λ°©λ²μ λ°°μ°κ² λ©λλ€.
Model Context Protocol (MCP)μ μ ν리μΌμ΄μ μ΄ LLMμ 컨ν μ€νΈλ₯Ό μ 곡νλ λ°©μμ νμ€ννλ μ€ν νλ‘ν μ½μ λλ€. MCPλ AI μ ν리μΌμ΄μ μ μν USB-C ν¬νΈμ κ°μμ AI λͺ¨λΈμ λ€μν λ°μ΄ν° μμ€ λ° λꡬμ μ°κ²°νλ νμ€νλ λ°©λ²μ μ 곡ν©λλ€.
μ΄ μμ μ λ§μΉλ©΄ λ€μμ μνν μ μμ΅λλ€:
MCP μμ μ μμνκΈ° μ μ κ°λ° νκ²½μ μ€λΉνκ³ κΈ°λ³Έ μμ νλ¦μ μ΄ν΄νλ κ²μ΄ μ€μν©λλ€. μ΄ μΉμ μ MCP μμμ μννκ² νκΈ° μν μ΄κΈ° μ€μ λ¨κ³λ₯Ό μλ΄ν©λλ€.
MCP κ°λ°μ μ°©μνκΈ° μ μ λ€μμ΄ μ€λΉλμλμ§ νμΈνμΈμ:
MCP μλ²λ μΌλ°μ μΌλ‘ λ€μμ ν¬ν¨ν©λλ€:
μλλ TypeScript μμ μ λλ€:
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// MCP μλ²λ₯Ό μμ±ν©λλ€
const server = new McpServer({
name: "Demo",
version: "1.0.0"
});
// μΆκ° λꡬλ₯Ό μΆκ°ν©λλ€
server.tool("add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// λμ μΈμ¬λ§ 리μμ€λ₯Ό μΆκ°ν©λλ€
server.resource(
"file",
// 'list' λ§€κ°λ³μλ 리μμ€κ° μ¬μ© κ°λ₯ν νμΌμ λμ΄νλ λ°©μμ μ μ΄ν©λλ€. undefinedλ‘ μ€μ νλ©΄ μ΄ λ¦¬μμ€μ λͺ©λ‘ νμκ° λΉνμ±νλ©λλ€.
new ResourceTemplate("file://{path}", { list: undefined }),
async (uri, { path }) => ({
contents: [{
uri: uri.href,
text: `File, ${path}!`
}]
})
);
// νμΌ λ΄μ©μ μ½λ νμΌ λ¦¬μμ€λ₯Ό μΆκ°ν©λλ€
server.resource(
"file",
new ResourceTemplate("file://{path}", { list: undefined }),
async (uri, { path }) => {
let text;
try {
text = await fs.readFile(path, "utf8");
} catch (err) {
text = `Error reading file: ${err.message}`;
}
return {
contents: [{
uri: uri.href,
text
}]
};
}
);
server.prompt(
"review-code",
{ code: z.string() },
({ code }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please review this code:\n\n${code}`
}
}]
})
);
// stdinμμ λ©μμ§λ₯Ό λ°κ³ stdoutμΌλ‘ λ©μμ§λ₯Ό μ μ‘νκΈ° μμν©λλ€
const transport = new StdioServerTransport();
await server.connect(transport);
μ μ½λμμ μ°λ¦¬λ:
calculator)λ₯Ό λ±λ‘νμ΅λλ€.MCP μλ²λ₯Ό ν μ€νΈνκΈ° μ μ μ΄μ© κ°λ₯ν λꡬ λ° λλ²κΉ λͺ¨λ² μ¬λ‘λ₯Ό μ΄ν΄νλ κ²μ΄ μ€μν©λλ€. ν¨κ³Όμ μΈ ν μ€νΈλ μλ²κ° μμλλ‘ μλνλμ§ νμΈνκ³ λ¬Έμ λ₯Ό μ μν νμ λ° ν΄κ²°νλ λ° λμμ΄ λ©λλ€. λ€μ μΉμ μμ MCP ꡬνμ κ²μ¦νκΈ° μν κΆμ₯ λ°©λ²μ μ€λͺ ν©λλ€.
MCPλ μλ² ν μ€νΈ λ° λλ²κΉ μ λμμ£Όλ λꡬλ₯Ό μ 곡ν©λλ€:
1. μλ² κΈ°λ₯ νμ: μ¬μ© κ°λ₯ν 리μμ€, λꡬ, ν둬ννΈ μλ κ°μ§
2. λꡬ μ€ν ν μ€νΈ: λ€μν λ§€κ°λ³μλ‘ μ€μκ° μλ΅ νμΈ
3. μλ² λ©νλ°μ΄ν° μ‘°ν: μλ² μ 보, μ€ν€λ§, κ΅¬μ± κ²ν
# μμ TypeScript, MCP Inspector μ€μΉ λ° μ€ν
npx @modelcontextprotocol/inspector node build/index.js
μ λͺ λ Ήμ΄λ₯Ό μ€ννλ©΄ MCP Inspectorκ° λΈλΌμ°μ μμ λ‘컬 μΉ μΈν°νμ΄μ€λ₯Ό μ€νν©λλ€. λ±λ‘λ MCP μλ², μ¬μ© κ°λ₯ν λꡬ, 리μμ€ λ° ν둬ννΈ λμ보λλ₯Ό λ³Ό μ μμ΅λλ€. μ΄ μΈν°νμ΄μ€λ‘ λꡬ μ€ν ν μ€νΈ, μλ² λ©νλ°μ΄ν° μ‘°μ¬, μ€μκ° μλ΅ νμΈ λ±μ΄ κ°λ₯ν΄ MCP μλ² κ΅¬ν κ²μ¦ λ° λλ²κΉ μ΄ μμν΄μ§λλ€.
λ€μμ νλ©΄ μμμ λλ€:
| λ¬Έμ | κ°λ₯ν ν΄κ²°μ± |
|-------------------------|--------------------------------------------|
| μ°κ²° κ±°λΆλ¨ | μλ² μ€ν μ¬λΆ λ° ν¬νΈ νμΈ |
| λꡬ μ€ν μ€λ₯ | λ§€κ°λ³μ κ²μ¦ λ° μ€λ₯ μ²λ¦¬ κ²ν |
| μΈμ¦ μ€ν¨ | API ν€ λ° κΆν νμΈ |
| μ€ν€λ§ κ²μ¦ μ€λ₯ | λ§€κ°λ³μκ° μ μλ μ€ν€λ§μ μΌμΉνλμ§ νμΈ|
| μλ²κ° μμλμ§ μμ | ν¬νΈ μΆ©λ λλ λλ½λ μ’ μμ± μ κ² |
| CORS μ€λ₯ | κ΅μ°¨ μΆμ² μμ²μ μ μ ν CORS ν€λ κ΅¬μ± |
| μΈμ¦ λ¬Έμ | ν ν° μ ν¨μ± λ° κΆν νμΈ |
λ‘컬 κ°λ° λ° ν μ€νΈμ©μΌλ‘, MCP μλ²λ₯Ό μμ μ λ¨Έμ μμ μ§μ μ€νν μ μμ΅λλ€:
1. μλ² νλ‘μΈμ€ μμ: MCP μλ² μ ν리μΌμ΄μ μ€ν
2. λ€νΈμνΉ κ΅¬μ±: μλ²κ° μμ ν¬νΈμμ μ κ·Ό κ°λ₯νκ² μ€μ
3. ν΄λΌμ΄μΈνΈ μ°κ²°: http://localhost:3000 κ°μ λ‘컬 μ°κ²° URL μ¬μ©
# μμ: TypeScript MCP μλ²λ₯Ό λ‘컬μμ μ€ννκΈ°
npm run start
# μλ²κ° http://localhost:3000 μμ μ€ν μ€μ
λλ€
μ΄μ μμ μμ ν΅μ¬ κ°λ μ λ€λ€μΌλ μ΄μ κ·Έ μ§μμ μ€μ΅ν΄ λ³΄κ² μ΅λλ€.
μ½λ©μ μμνκΈ° μ μ μλ²μ μν μ μκΈ°ν΄ λ΄ μλ€:
MCP μλ²λ μλ₯Ό λ€μ΄:
μ’μ΅λλ€, 무μμ ν μ μλμ§ μμμΌλ μ½λ©μ μμν΄ λ΄ μλ€.
μλ²λ₯Ό λ§λ€λ €λ©΄ λ€μ λ¨κ³λ₯Ό λ°λ₯΄μΈμ:
# νλ‘μ νΈ λλ ν 리λ₯Ό μμ±νκ³ npm νλ‘μ νΈλ₯Ό μ΄κΈ°ννμμμ€
mkdir calculator-server
cd calculator-server
npm init -y
# νλ‘μ νΈ λλ ν 리 μμ±
mkdir calculator-server
cd calculator-server
# Visual Studio Codeμμ ν΄λ μ΄κΈ° - λ€λ₯Έ IDEλ₯Ό μ¬μ©νλ κ²½μ° μλ΅νμΈμ
code .
dotnet new console -n McpCalculatorServer
cd McpCalculatorServer
Javaμ κ²½μ° Spring Boot νλ‘μ νΈλ₯Ό λ§λμΈμ:
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
# μ νμ μΌλ‘ μ¬μ©νμ§ μλ ν
μ€νΈ μ κ±°
rm -rf src/test/java
*pom.xml* νμΌμ λ€μκ³Ό κ°μ μ 체 ꡬμ±μ μΆκ°νμΈμ:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot parent for dependency management -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath />
</parent>
<!-- Project coordinates -->
<groupId>com.example</groupId>
<artifactId>calculator-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Calculator Server</name>
<description>Basic calculator MCP service for beginners</description>
<!-- Properties -->
<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<!-- Spring AI BOM for version management -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Dependencies -->
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Build configuration -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>21</release>
</configuration>
</plugin>
</plugins>
</build>
<!-- Repositories for Spring AI snapshots -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>
mkdir calculator-server
cd calculator-server
cargo init
νλ‘μ νΈλ₯Ό μμ±νμΌλ λ€μμ μμ‘΄μ± μΆκ°μ λλ€:
# μμ§ μ€μΉνμ§ μμ κ²½μ° TypeScriptλ₯Ό μ μμ μ€μΉνμΈμ
npm install typescript -g
# MCP SDKμ μ€ν€λ§ κ²μ¦μ μν΄ Zodλ₯Ό μ€μΉνμΈμ
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
# κ°μ νκ²½μ λ§λ€κ³ μ’
μμ±μ μ€μΉν©λλ€
python -m venv venv
venv\Scripts\activate
pip install "mcp[cli]"
cd calculator-server
./mvnw clean install -DskipTests
cargo add rmcp --features server,transport-io
cargo add serde
cargo add tokio --features rt-multi-thread
*package.json* νμΌμ μ΄μ΄ λ€μ λ΄μ©μΌλ‘ κ΅μ²΄ν΄ μλ² λΉλ λ° μ€νμ΄ κ°λ₯νκ² ν©λλ€:
{
"name": "calculator-server",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "npm run build && node ./build/index.js",
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "A simple calculator server using Model Context Protocol",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.16.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/node": "^24.0.14",
"typescript": "^5.8.3"
}
}
*tsconfig.json* νμΌμ λ€μ λ΄μ©μΌλ‘ μμ±νμΈμ:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
μμ€ μ½λμ© λλ ν°λ¦¬λ₯Ό μμ±ν©λλ€:
mkdir src
touch src/index.ts
*server.py* νμΌμ μμ±νμΈμ
touch server.py
νμν NuGet ν¨ν€μ§λ₯Ό μ€μΉνμΈμ:
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting
Java Spring Boot νλ‘μ νΈλ νλ‘μ νΈ κ΅¬μ‘°κ° μλμΌλ‘ μμ±λ©λλ€.
Rustλ cargo init μ€ν μ κΈ°λ³Έμ μΌλ‘ *src/main.rs* νμΌμ΄ μμ±λ©λλ€. ν΄λΉ νμΌμ μ΄κ³ κΈ°λ³Έ μ½λλ₯Ό μμ νμΈμ.
*index.ts* νμΌμ μμ±νκ³ λ€μ μ½λλ₯Ό μΆκ°νμΈμ:
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// MCP μλ² μμ±
const server = new McpServer({
name: "Calculator MCP Server",
version: "1.0.0"
});
μλ²κ° μμ±λμμΌλ ν μΌμ΄ λ§μ§ μμ΅λλ€. κ³ μ³ λ΄ μλ€.
# server.py
from mcp.server.fastmcp import FastMCP
# MCP μλ² μμ±
mcp = FastMCP("Demo")
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 =>
{
// Configure all logs to go to stderr
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
// add features
Javaλ ν΅μ¬ μλ² κ΅¬μ± μμλ₯Ό μμ±ν©λλ€. λ¨Όμ λ©μΈ μ ν리μΌμ΄μ ν΄λμ€λ₯Ό μμ νμΈμ:
*src/main/java/com/microsoft/mcp/sample/server/McpServerApplication.java*:
package com.microsoft.mcp.sample.server;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.microsoft.mcp.sample.server.service.CalculatorService;
@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();
}
}
κ³μ°κΈ° μλΉμ€ μμ± *src/main/java/com/microsoft/mcp/sample/server/service/CalculatorService.java*:
package com.microsoft.mcp.sample.server.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
/**
* Service for basic calculator operations.
* This service provides simple calculator functionality through MCP.
*/
@Service
public class CalculatorService {
/**
* Add two numbers
* @param a The first number
* @param b The second number
* @return The sum of the two numbers
*/
@Tool(description = "Add two numbers together")
public String add(double a, double b) {
double result = a + b;
return formatResult(a, "+", b, result);
}
/**
* Subtract one number from another
* @param a The number to subtract from
* @param b The number to subtract
* @return The result of the subtraction
*/
@Tool(description = "Subtract the second number from the first number")
public String subtract(double a, double b) {
double result = a - b;
return formatResult(a, "-", b, result);
}
/**
* Multiply two numbers
* @param a The first number
* @param b The second number
* @return The product of the two numbers
*/
@Tool(description = "Multiply two numbers together")
public String multiply(double a, double b) {
double result = a * b;
return formatResult(a, "*", b, result);
}
/**
* Divide one number by another
* @param a The numerator
* @param b The denominator
* @return The result of the division
*/
@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";
}
double result = a / b;
return formatResult(a, "/", b, result);
}
/**
* Calculate the power of a number
* @param base The base number
* @param exponent The exponent
* @return The result of raising the base to the exponent
*/
@Tool(description = "Calculate the power of a number (base raised to an exponent)")
public String power(double base, double exponent) {
double result = Math.pow(base, exponent);
return formatResult(base, "^", exponent, result);
}
/**
* Calculate the square root of a number
* @param number The number to find the square root of
* @return The square root of the number
*/
@Tool(description = "Calculate the square root of a number")
public String squareRoot(double number) {
if (number < 0) {
return "Error: Cannot calculate square root of a negative number";
}
double result = Math.sqrt(number);
return String.format("β%.2f = %.2f", number, result);
}
/**
* Calculate the modulus (remainder) of division
* @param a The dividend
* @param b The divisor
* @return The remainder of the division
*/
@Tool(description = "Calculate the remainder when one number is divided by another")
public String modulus(double a, double b) {
if (b == 0) {
return "Error: Cannot divide by zero";
}
double result = a % b;
return formatResult(a, "%", b, result);
}
/**
* Calculate the absolute value of a number
* @param number The number to find the absolute value of
* @return The absolute value of the number
*/
@Tool(description = "Calculate the absolute value of a number")
public String absolute(double number) {
double result = Math.abs(number);
return String.format("|%.2f| = %.2f", number, result);
}
/**
* Get help about available calculator operations
* @return Information about available operations
*/
@Tool(description = "Get help about available calculator operations")
public String help() {
return "Basic Calculator MCP Service\n\n" +
"Available operations:\n" +
"1. add(a, b) - Adds two numbers\n" +
"2. subtract(a, b) - Subtracts the second number from the first\n" +
"3. multiply(a, b) - Multiplies two numbers\n" +
"4. divide(a, b) - Divides the first number by the second\n" +
"5. power(base, exponent) - Raises a number to a power\n" +
"6. squareRoot(number) - Calculates the square root\n" +
"7. modulus(a, b) - Calculates the remainder of division\n" +
"8. absolute(number) - Calculates the absolute value\n\n" +
"Example usage: add(5, 3) will return 5 + 3 = 8";
}
/**
* Format the result of a calculation
*/
private String formatResult(double a, String operator, double b, double result) {
return String.format("%.2f %s %.2f = %.2f", a, operator, b, result);
}
}
νλ‘λμ μ€λΉ μλΉμ€λ₯Ό μν μ νμ κ΅¬μ± μμ:
μμ κ΅¬μ± μμ± *src/main/java/com/microsoft/mcp/sample/server/config/StartupConfig.java*:
package com.microsoft.mcp.sample.server.config;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class StartupConfig {
@Bean
public CommandLineRunner startupInfo() {
return args -> {
System.out.println("\n" + "=".repeat(60));
System.out.println("Calculator MCP Server is starting...");
System.out.println("SSE endpoint: http://localhost:8080/sse");
System.out.println("Health check: http://localhost:8080/actuator/health");
System.out.println("=".repeat(60) + "\n");
};
}
}
ν¬μ€ 컨νΈλ‘€λ¬ μμ± *src/main/java/com/microsoft/mcp/sample/server/controller/HealthController.java*:
package com.microsoft.mcp.sample.server.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HealthController {
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> healthCheck() {
Map<String, Object> response = new HashMap<>();
response.put("status", "UP");
response.put("timestamp", LocalDateTime.now().toString());
response.put("service", "Calculator MCP Server");
return ResponseEntity.ok(response);
}
}
μμΈ νΈλ€λ¬ μμ± *src/main/java/com/microsoft/mcp/sample/server/exception/GlobalExceptionHandler.java*:
package com.microsoft.mcp.sample.server.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
ErrorResponse error = new ErrorResponse(
"Invalid_Input",
"Invalid input parameter: " + ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
public static class ErrorResponse {
private String code;
private String message;
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
}
// κ²ν°
public String getCode() { return code; }
public String getMessage() { return message; }
}
}
컀μ€ν λ°°λ μμ± *src/main/resources/banner.txt*:
_____ _ _ _
/ ____| | | | | | |
| | __ _| | ___ _ _| | __ _| |_ ___ _ __
| | / _` | |/ __| | | | |/ _` | __/ _ \| '__|
| |___| (_| | | (__| |_| | | (_| | || (_) | |
\_____\__,_|_|\___|\__,_|_|\__,_|\__\___/|_|
Calculator MCP Server v1.0
Spring Boot MCP Application
*src/main.rs* νμΌ μλ¨μ λ€μ μ½λλ₯Ό μΆκ°νμΈμ. μ΄λ MCP μλ²μ νμν λΌμ΄λΈλ¬λ¦¬μ λͺ¨λμ κ°μ Έμ΅λλ€.
use rmcp::{
handler::server::{router::tool::ToolRouter, tool::Parameters},
model::{ServerCapabilities, ServerInfo},
schemars, tool, tool_handler, tool_router,
transport::stdio,
ServerHandler, ServiceExt,
};
use std::error::Error;
κ³μ°κΈ° μλ²λ λ μ«μλ₯Ό λνλ κ°λ¨ν μλ²κ° λ κ²μ λλ€. κ³μ°κΈ° μμ²μ λνλ΄λ structλ₯Ό λ§λ€μ΄ λ΄ μλ€.
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CalculatorRequest {
pub a: f64,
pub b: f64,
}
λ€μμΌλ‘ κ³μ°κΈ° μλ²λ₯Ό λνλ΄λ structλ₯Ό λ§λλλ€. μ΄ structλ λꡬ λΌμ°ν°λ₯Ό 보μ νλ©° λꡬ λ±λ‘μ μ¬μ©λ©λλ€.
#[derive(Debug, Clone)]
pub struct Calculator {
tool_router: ToolRouter<Self>,
}
μ΄μ Calculator structλ₯Ό ꡬννμ¬ μλ² μ μΈμ€ν΄μ€λ₯Ό μμ±νκ³ μλ² μ 보λ₯Ό μ 곡νλ νΈλ€λ¬λ₯Ό ꡬνν©λλ€.
#[tool_router]
impl Calculator {
pub fn new() -> Self {
Self {
tool_router: Self::tool_router(),
}
}
}
#[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()
}
}
}
λ§μ§λ§μΌλ‘ μλ²λ₯Ό μμνλ main ν¨μλ₯Ό ꡬνν΄μΌ ν©λλ€. μ΄ ν¨μλ Calculator struct μΈμ€ν΄μ€λ₯Ό λ§λ€κ³ νμ€ μ
μΆλ ₯μΌλ‘ μλ²λ₯Ό μ΄μν©λλ€.
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let service = Calculator::new().serve(stdio()).await?;
service.waiting().await?;
Ok(())
}
μλ²λ μ΄μ μ체μ κ΄ν κΈ°λ³Έ μ 보λ₯Ό μ 곡ν©λλ€. λ€μμΌλ‘ λ§μ μ μννλ λꡬλ₯Ό μΆκ°ν©λλ€.
λ€μ μ½λλ‘ λꡬμ 리μμ€λ₯Ό μΆκ°νμΈμ:
server.tool(
"add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
}]
})
);
λꡬλ a λ° b λ§€κ°λ³μλ₯Ό λ°κ³ , λ€μ νμμ μλ΅μ μμ±ν©λλ€:
{
contents: [{
type: "text", content: "some content"
}]
}
리μμ€λ λ¬Έμμ΄ "greeting"μΌλ‘ μ κ·Όνλ©°, μ΄λ¦(name) λ§€κ°λ³μλ₯Ό λ°μ λꡬμ μ μ¬ν μλ΅μ μμ±ν©λλ€:
{
uri: "<href>",
text: "a text"
}
# λ§μ
λꡬ μΆκ°
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# λμ μΈμ¬λ§ 리μμ€ μΆκ°
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
μ μ½λμμ μ°λ¦¬λ:
aμ bλΌλ μ μ λ§€κ°λ³μλ₯Ό λ°λ add λꡬλ₯Ό μ μνμ΅λλ€.name λ§€κ°λ³μλ₯Ό λ°λ greeting 리μμ€λ₯Ό λ§λ€μμ΅λλ€.Program.cs νμΌμ λ€μμ μΆκ°νμΈμ:
[McpServerToolType]
public static class CalculatorTool
{
[McpServerTool, Description("Adds two numbers")]
public static string Add(int a, int b) => $"Sum {a + b}";
}
λꡬλ μ΄μ λ¨κ³μμ μ΄λ―Έ μμ±νμ΅λλ€.
impl Calculator λΈλ‘ λ΄μ μ λꡬλ₯Ό μΆκ°νμΈμ:
#[tool(description = "Adds a and b")]
async fn add(
&self,
Parameters(CalculatorRequest { a, b }): Parameters<CalculatorRequest>,
) -> String {
(a + b).to_string()
}
μλ²κ° μμν μ μλλ‘ λ§μ§λ§ μ½λλ₯Ό μΆκ°ν©μλ€:
// stdinμμ λ©μμ§ μμ μ μμνκ³ stdoutμμ λ©μμ§ μ μ‘μ μμν©λλ€
const transport = new StdioServerTransport();
await server.connect(transport);
μ 체 μ½λλ λ€μκ³Ό κ°μ΅λλ€:
// index.ts
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// MCP μλ² μμ±
const server = new McpServer({
name: "Calculator MCP Server",
version: "1.0.0"
});
// μΆκ° λꡬ μΆκ°
server.tool(
"add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// λμ μΈμ¬λ§ 리μμ€ μΆκ°
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
}]
})
);
// stdinμμ λ©μμ§ μμ μμ λ° stdoutμΌλ‘ λ©μμ§ μ μ‘ μμ
const transport = new StdioServerTransport();
server.connect(transport);
# server.py
from mcp.server.fastmcp import FastMCP
# MCP μλ² μμ±
mcp = FastMCP("Demo")
# μΆκ° λꡬ μΆκ°
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
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()
λ€μ λ΄μ©μ κ°μ§ 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 =>
{
// Configure all logs to go to stderr
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
package com.microsoft.mcp.sample.server;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.microsoft.mcp.sample.server.service.CalculatorService;
@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();
}
}
Rust μλ²μ μ΅μ’ μ½λλ λ€μκ³Ό κ°μ΅λλ€:
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(())
}
λ€μ λͺ λ Ήμ΄λ‘ μλ²λ₯Ό μμνμΈμ:
npm run build
mcp run server.py
> MCP Inspectorλ₯Ό μ¬μ©νλ €λ©΄ mcp dev server.pyλ₯Ό μ¬μ©νμΈμ.
μ΄λ Inspectorλ₯Ό μλμΌλ‘ μ€ννκ³ νμν νλ‘μ μΈμ
ν ν°μ μ 곡ν©λλ€. mcp run server.pyλ₯Ό μ¬μ©ν κ²½μ° Inspectorλ₯Ό μλμΌλ‘ μμνκ³ μ°κ²°μ ꡬμ±ν΄μΌ ν©λλ€.
νλ‘μ νΈ λλ ν°λ¦¬ μμ μλμ§ νμΈνμΈμ:
cd McpCalculatorServer
dotnet run
./mvnw clean install -DskipTests
java -jar target/calculator-server-0.0.1-SNAPSHOT.jar
μλ²λ₯Ό νμννκ³ μ€ννλ €λ©΄ λ€μ λͺ λ Ήμ΄λ₯Ό μ€ννμΈμ:
cargo fmt
cargo run
Inspectorλ μλ²λ₯Ό μμνκ³ μνΈμμ©ν μ μλλ‘ λμμ£Όλ νλ₯ν λꡬμ λλ€. μμν΄ λ΄ μλ€:
> [!NOTE]
> "command" νλμ λ΄μ©μ νΉμ λ°νμμΌλ‘ μλ²λ₯Ό μ€ννλ λͺ λ Ήμ΄λ₯Ό ν¬ν¨νλ―λ‘ λ€λ₯΄κ² λ³΄μΌ μ μμ΅λλ€.
npx @modelcontextprotocol/inspector node build/index.js
λλ package.jsonμ "inspector": "npx @modelcontextprotocol/inspector node build/index.js"λ₯Ό μΆκ°νκ³ npm run inspectorλ₯Ό μ€ννμΈμ.
Pythonμ Node.js λκ΅¬μΈ inspectorλ₯Ό λνν©λλ€. λ€μκ³Ό κ°μ΄ ν΄λΉ λꡬλ₯Ό νΈμΆν μ μμ΅λλ€:
mcp dev server.py
νμ§λ§ μ 체 λͺ λ Ήμ΄λ₯Ό ꡬννμ§ μμΌλ―λ‘ Node.js λꡬλ₯Ό μ§μ μ€ννλ κ²μ΄ κΆμ₯λ©λλ€:
npx @modelcontextprotocol/inspector mcp run server.py
μ€ν¬λ¦½νΈ μ€νμ μν λͺ λ Ήκ³Ό μΈμλ₯Ό ꡬμ±ν μ μλ λꡬλ IDEλ₯Ό μ¬μ©νλ κ²½μ°,
Command νλμ pythonμ μ€μ νκ³ Argumentsμ server.pyλ₯Ό μ€μ ν΄μΌ ν©λλ€.
μ΄λ κ² ν΄μΌ μ€ν¬λ¦½νΈκ° μ¬λ°λ₯΄κ² μ€νλ©λλ€.
νλ‘μ νΈ λλ ν°λ¦¬μ μλμ§ νμΈνμΈμ:
cd McpCalculatorServer
npx @modelcontextprotocol/inspector dotnet run
κ³μ°κΈ° μλ²κ° μ€ν μ€μΈμ§ νμΈνμΈμ
κ·Έλ° λ€μ μΈμ€νν°λ₯Ό μ€νν©λλ€:
npx @modelcontextprotocol/inspector
μΈμ€νν° μΉ μΈν°νμ΄μ€μμ:
1. μ μ‘ μ νμΌλ‘ "SSE"λ₯Ό μ ννμΈμ
2. URLμ http://localhost:8080/sseλ‘ μ€μ νμΈμ
3. "Connect"λ₯Ό ν΄λ¦νμΈμ
μ΄μ μλ²μ μ°κ²°λμμ΅λλ€
Java μλ² ν μ€νΈ μΉμ μ΄ μλ£λμμ΅λλ€
λ€μ μΉμ μ μλ²μ μνΈμμ©νλ λ°©λ²μ κ΄ν λ΄μ©μ λλ€.
λ€μκ³Ό κ°μ μ¬μ©μ μΈν°νμ΄μ€κ° λ³΄μΌ κ²μ λλ€:
1. "Connect" λ²νΌμ μ ννμ¬ μλ²μ μ°κ²°νμΈμ
μλ²μ μ°κ²°λλ©΄ λ€μ νλ©΄μ΄ λ³΄μ λλ€:
1. "Tools"μμ "listTools"λ₯Ό μ ννμΈμ. "Add"κ° νμλλ©΄ "Add"λ₯Ό μ ννκ³ λ§€κ°λ³μ κ°μ μ λ ₯νμΈμ.
λ€μκ³Ό κ°μ μλ΅, μ¦ "add" λꡬμ κ²°κ³Όκ° νμλ©λλ€:
μΆνν©λλ€, 첫 λ²μ§Έ μλ²λ₯Ό μ±κ³΅μ μΌλ‘ λ§λ€κ³ μ€ννμ΅λλ€!
MCP μΈμ€νν° CLIλ‘ Rust μλ²λ₯Ό μ€ννλ €λ©΄ λ€μ λͺ λ Ήμ΄λ₯Ό μ¬μ©νμΈμ:
npx @modelcontextprotocol/inspector cargo run --cli --method tools/call --tool-name add --tool-arg a=1 b=2
MCPλ μ¬λ¬ μΈμ΄μ λν 곡μ SDKλ₯Ό μ 곡ν©λλ€:
μ νν λꡬλ₯Ό μ¬μ©νμ¬ κ°λ¨ν MCP μλ²λ₯Ό λ§λμΈμ:
1. μ νΈνλ μΈμ΄(.NET, Java, Python, TypeScript, Rust)λ‘ λꡬλ₯Ό ꡬννμΈμ.
2. μ λ ₯ λ§€κ°λ³μμ λ°ν κ°μ μ μνμΈμ.
3. μΈμ€νν° λꡬλ₯Ό μ€ννμ¬ μλ²κ° μ λλ‘ μλνλμ§ νμΈνμΈμ.
4. λ€μν μ λ ₯μΌλ‘ ꡬνμ ν μ€νΈνμΈμ.
λ€μ: MCP ν΄λΌμ΄μΈνΈ μμνκΈ°
---
λ©΄μ± μ‘°ν:
μ΄ λ¬Έμλ AI λ²μ μλΉμ€ Co-op Translatorλ₯Ό μ¬μ©νμ¬ λ²μλμμ΅λλ€.
μ νμ±μ μν΄ λ Έλ ₯νκ³ μμΌλ μλ λ²μμλ μ€λ₯λ λΆμ νμ±μ΄ ν¬ν¨λ μ μμμ μ μν΄ μ£ΌμκΈ° λ°λλλ€.
μλ³Έ λ¬Έμκ° μμ΄λ‘ λ 곡μ μλ£μμ μ°Έκ³ νμκΈ° λ°λλλ€.
μ€μν μ 보μ λν΄μλ μ λ¬Έ μΈκ° λ²μκ°μ λ²μμ κΆμ₯ν©λλ€.
λ³Έ λ²μ μ¬μ©μΌλ‘ μΈν΄ λ°μνλ λͺ¨λ μ€ν΄λ μ€μμ λν΄ λΉμ¬λ μ± μμ μ§μ§ μμ΅λλ€.