šBuilding Your First MCP Server: A Complete Guide
Source: Dev.to
Introduction
The Model Context Protocol (MCP) is revolutionizing how AI applications interact with external tools and data sources. Whether youāre building Claude integrations, VSCode extensions, or custom AI workflows, understanding MCP servers is essential for modern AI development.
In this guide weāll build a fully functional MCP server from scratch, deploy it to the cloud, and connect it to popular MCP clients. By the end, youāll have a working weatherāinformation server that any MCP client can use.
MCP (Model Context Protocol) is an open protocol that standardizes how AI applications connect to external data sources and tools. Think of it as a universal adapter that allows AI models like Claude to safely access your databases, APIs, file systems, and business tools.
- Standardized Integration: One protocol works across all MCPācompatible clients
- Security First: Builtāin authentication and permission controls
- Flexible Architecture: Support for local and remote servers
- Tool Discovery: Clients automatically discover available capabilities
An MCP server consists of three main components:
- Resources ā Data sources the server can provide (files, database records, API data)
- Tools ā Actions the server can perform (create, update, delete operations)
- Prompts ā Preādefined templates for common tasks
Below weāll build a practical MCP server that provides weather information using a public API.
Project Setup
mkdir weather-mcp-server
cd weather-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk axios dotenv
npm install -D @types/node typescript
Project Structure
weather-mcp-server/
āāā src/
ā āāā index.ts
ā āāā server.ts
ā āāā weatherService.ts
āāā package.json
āāā tsconfig.json
āāā .env
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
package.json (scripts)
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsc && node dist/index.js"
},
"type": "module"
}
Weather Service (src/weatherService.ts)
import axios from 'axios';
export interface WeatherData {
location: string;
temperature: number;
condition: string;
humidity: number;
windSpeed: number;
description: string;
}
export class WeatherService {
private apiKey: string;
private baseUrl = 'https://api.openweathermap.org/data/2.5';
constructor(apiKey: string) {
this.apiKey = apiKey;
}
async getCurrentWeather(city: string): Promise {
try {
const response = await axios.get(`${this.baseUrl}/weather`, {
params: {
q: city,
appid: this.apiKey,
units: 'metric'
}
});
const data = response.data;
return {
location: `${data.name}, ${data.sys.country}`,
temperature: Math.round(data.main.temp),
condition: data.weather[0].main,
humidity: data.main.humidity,
windSpeed: data.wind.speed,
description: data.weather[0].description
};
} catch (error) {
throw new Error(`Failed to fetch weather data: ${error}`);
}
}
async getForecast(city: string, days: number = 5): Promise {
try {
const response = await axios.get(`${this.baseUrl}/forecast`, {
params: {
q: city,
appid: this.apiKey,
units: 'metric',
cnt: days * 8 // API returns 3āhour intervals
}
});
return response.data;
} catch (error) {
throw new Error(`Failed to fetch forecast data: ${error}`);
}
}
}
MCP Server (src/server.ts)
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import { WeatherService } from './weatherService.js';
export class WeatherMCPServer {
private server: Server;
private weatherService: WeatherService;
constructor(apiKey: string) {
this.weatherService = new WeatherService(apiKey);
this.server = new Server(
{
name: 'weather-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {}
},
}
);
this.setupHandlers();
}
private setupHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_current_weather',
description: 'Get current weather information for a city',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'City name (e.g., "London", "New York")'
}
},
required: ['city']
}
},
{
name: 'get_forecast',
description: 'Get weather forecast for upcoming days',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'City name'
},
days: {
type: 'number',
description: 'Number of days (1-5)',
default: 5
}
},
required: ['city']
}
}
]
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === 'get_current_weather') {
const weather = await this.weatherService.getCurrentWeather(
args.city as string
);
return {
content: [
{
type: 'text',
text: JSON.stringify(weather, null, 2)
}
]
};
}
if (name === 'get_forecast') {
const forecast = await this.weatherService.getForecast(
args.city as string,
(args.days as number) || 5
);
return {
content: [
{
type: 'text',
text: JSON.stringify(forecast, null, 2)
}
]
};
}
throw new Error(`Unknown tool: ${name}`);
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error}`
}
],
isError: true
};
}
});
// List available resources
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: 'weather://cities',
name: 'Popular Cities',
description: 'List of popular cities for weather queries',
mimeType: 'application/json'
}
]
}));
// Read resources
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === 'weather://cities') {
const cities = [
'London', 'New York', 'Tokyo', 'Paris', 'Sydney',
'Berlin', 'Toronto', 'Singapore', 'Dubai', 'Mumbai'
];
return {
contents: [
{
type: 'application/json',
data: JSON.stringify(cities, null, 2)
}
]
};
}
// Fallback for unknown resources
return {
contents: [],
isError: true,
errorMessage: `Resource not found: ${uri}`
};
});
}
// Start the server (e.g., using stdio transport)
public start() {
const transport = new StdioServerTransport(this.server);
transport.start();
}
}
Running the Server
Create a .env file with your OpenWeatherMap API key:
OPENWEATHER_API_KEY=your_api_key_here
Then start the server:
npm run build
npm start
The server will listen on standard I/O, ready for any MCPācompatible client (e.g., Claude) to discover the get_current_weather and get_forecast tools, as well as the weather://cities resource.