Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mcp-use/mcp-use/llms.txt

Use this file to discover all available pages before exploring further.

Introduction

In this guide, you’ll build a complete MCP server from scratch that includes:
  • 🛠️ Tools for performing actions
  • 📋 Resources for accessing data
  • 📝 Prompts for guiding AI behavior
  • 🔍 Inspector for testing and debugging
By the end, you’ll have a fully functional weather information server.

Prerequisites

  • Node.js 20.19.0 or higher
  • npm, yarn, or pnpm
  • Basic TypeScript knowledge

Project Setup

1

Create Project Directory

mkdir weather-server
cd weather-server
npm init -y
2

Install Dependencies

npm install mcp-use zod
npm install -D typescript tsx @types/node
3

Create tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
4

Create src/server.ts

mkdir src
touch src/server.ts

Step 1: Basic Server Setup

Let’s start with a minimal server:
src/server.ts
import { MCPServer } from "mcp-use/server";

const server = new MCPServer({
  name: "weather-server",
  version: "1.0.0",
  description: "A server providing weather information",
});

await server.listen(3000);
console.log("✅ Server running at http://localhost:3000");
console.log("🔍 Inspector: http://localhost:3000/inspector");
Run it:
npx tsx src/server.ts
Open http://localhost:3000/inspector in your browser. You’ll see the inspector with no tools yet.

Step 2: Add Your First Tool

Let’s add a tool to get current weather:
src/server.ts
import { MCPServer, text, object } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({
  name: "weather-server",
  version: "1.0.0",
  description: "A server providing weather information",
});

// Mock weather data (replace with real API)
const weatherData: Record<string, any> = {
  "san francisco": { temp: 65, condition: "Partly Cloudy", humidity: 70 },
  "new york": { temp: 45, condition: "Sunny", humidity: 55 },
  "tokyo": { temp: 55, condition: "Rainy", humidity: 80 },
  "london": { temp: 50, condition: "Cloudy", humidity: 75 },
};

server.tool({
  name: "get_current_weather",
  description: "Get current weather for a city",
  schema: z.object({
    city: z.string().describe("City name"),
    units: z.enum(["celsius", "fahrenheit"]).default("fahrenheit"),
  }),
}, async ({ city, units }) => {
  const cityKey = city.toLowerCase();
  const weather = weatherData[cityKey];
  
  if (!weather) {
    return text(`Weather data not available for ${city}`);
  }
  
  const temp = units === "celsius" 
    ? Math.round((weather.temp - 32) * 5/9)
    : weather.temp;
  
  return object({
    city,
    temperature: temp,
    units,
    condition: weather.condition,
    humidity: weather.humidity,
  });
});

await server.listen(3000);
Now test it in the inspector:
  1. Open http://localhost:3000/inspector
  2. Select “get_current_weather” tool
  3. Enter {"city": "San Francisco"}
  4. Click “Call Tool”
The inspector provides real-time testing without writing any client code!

Step 3: Add More Tools

Let’s add a forecast tool:
server.tool({
  name: "get_forecast",
  description: "Get weather forecast for the next N days",
  schema: z.object({
    city: z.string().describe("City name"),
    days: z.number().int().min(1).max(7).default(3),
  }),
}, async ({ city, days }) => {
  const cityKey = city.toLowerCase();
  const current = weatherData[cityKey];
  
  if (!current) {
    return text(`Weather data not available for ${city}`);
  }
  
  // Generate mock forecast
  const forecast = Array.from({ length: days }, (_, i) => ({
    day: i + 1,
    date: new Date(Date.now() + i * 24 * 60 * 60 * 1000).toDateString(),
    temp: current.temp + Math.floor(Math.random() * 10 - 5),
    condition: current.condition,
  }));
  
  return object({
    city,
    forecast,
  });
});

Step 4: Add Resources

Resources provide read-only data access:
import { markdown } from "mcp-use/server";

// Static resource
server.resource({
  name: "supported-cities",
  uri: "weather://cities",
  title: "Supported Cities",
  description: "List of cities with weather data",
}, async () => {
  const cities = Object.keys(weatherData);
  return object({
    cities: cities.map(c => c.charAt(0).toUpperCase() + c.slice(1)),
    count: cities.length,
  });
});

// Dynamic resource with URI template
server.resource({
  name: "city-info",
  uri: "weather://cities/{city}",
  title: "City Weather Info",
  description: "Detailed weather information for a specific city",
}, async ({ city }) => {
  const cityKey = city.toLowerCase();
  const weather = weatherData[cityKey];
  
  if (!weather) {
    return text(`No data for ${city}`);
  }
  
  return markdown(`
# Weather in ${city}

- **Temperature**: ${weather.temp}°F
- **Condition**: ${weather.condition}
- **Humidity**: ${weather.humidity}%

*Last updated: ${new Date().toLocaleString()}*
  `);
});
Resources are cached by clients, making them ideal for frequently accessed data.

Step 5: Add Prompts

Prompts help AI models use your tools effectively:
server.prompt({
  name: "weather-report",
  description: "Generate a comprehensive weather report",
  schema: z.object({
    city: z.string(),
    includeAdvice: z.boolean().default(true),
  }),
}, async ({ city, includeAdvice }) => {
  return text(`
Generate a weather report for ${city}:

1. Get current weather conditions
2. Get 3-day forecast
3. Summarize the information in a friendly tone
${includeAdvice ? "4. Provide practical advice (what to wear, activities, etc.)" : ""}

Format the report in a clear, conversational style.
  `);
});

server.prompt({
  name: "compare-cities",
  description: "Compare weather between two cities",
  schema: z.object({
    city1: z.string(),
    city2: z.string(),
  }),
}, async ({ city1, city2 }) => {
  return text(`
Compare the weather between ${city1} and ${city2}:

1. Get current weather for both cities
2. Compare temperatures, conditions, and humidity
3. Recommend which city has better weather today
4. Explain your reasoning
  `);
});

Step 6: Test Everything

1

Open Inspector

Navigate to http://localhost:3000/inspector (or :8000 for Python)
2

Test Tools

  • Try get_current_weather with different cities
  • Test get_forecast with various day counts
  • Experiment with different units
3

Access Resources

  • View supported-cities resource
  • Read city-info for specific cities
4

Try Prompts

  • Use weather-report prompt
  • Compare cities with compare-cities

Step 7: Add Error Handling

server.tool({
  name: "get_current_weather",
  description: "Get current weather for a city",
  schema: z.object({
    city: z.string().min(1).describe("City name"),
    units: z.enum(["celsius", "fahrenheit"]).default("fahrenheit"),
  }),
}, async ({ city, units }) => {
  try {
    const cityKey = city.toLowerCase().trim();
    
    if (!cityKey) {
      return object({
        error: "City name cannot be empty",
        code: "INVALID_INPUT",
      });
    }
    
    const weather = weatherData[cityKey];
    
    if (!weather) {
      return object({
        error: `Weather data not available for '${city}'`,
        code: "CITY_NOT_FOUND",
        availableCities: Object.keys(weatherData),
      });
    }
    
    const temp = units === "celsius" 
      ? Math.round((weather.temp - 32) * 5/9)
      : weather.temp;
    
    return object({
      success: true,
      city,
      temperature: temp,
      units,
      condition: weather.condition,
      humidity: weather.humidity,
    });
  } catch (error) {
    return object({
      error: "An unexpected error occurred",
      code: "INTERNAL_ERROR",
      message: error instanceof Error ? error.message : String(error),
    });
  }
});

Complete Server Code

import { MCPServer, text, object, markdown } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({
  name: "weather-server",
  version: "1.0.0",
  description: "A server providing weather information and forecasts",
});

const weatherData: Record<string, any> = {
  "san francisco": { temp: 65, condition: "Partly Cloudy", humidity: 70 },
  "new york": { temp: 45, condition: "Sunny", humidity: 55 },
  "tokyo": { temp: 55, condition: "Rainy", humidity: 80 },
  "london": { temp: 50, condition: "Cloudy", humidity: 75 },
};

// TOOLS
server.tool({
  name: "get_current_weather",
  description: "Get current weather for a city",
  schema: z.object({
    city: z.string().describe("City name"),
    units: z.enum(["celsius", "fahrenheit"]).default("fahrenheit"),
  }),
}, async ({ city, units }) => {
  const cityKey = city.toLowerCase();
  const weather = weatherData[cityKey];
  if (!weather) return text(`No data for ${city}`);
  
  const temp = units === "celsius" 
    ? Math.round((weather.temp - 32) * 5/9)
    : weather.temp;
  
  return object({
    city, temperature: temp, units,
    condition: weather.condition,
    humidity: weather.humidity,
  });
});

server.tool({
  name: "get_forecast",
  description: "Get weather forecast",
  schema: z.object({
    city: z.string(),
    days: z.number().int().min(1).max(7).default(3),
  }),
}, async ({ city, days }) => {
  const cityKey = city.toLowerCase();
  const current = weatherData[cityKey];
  if (!current) return text(`No data for ${city}`);
  
  const forecast = Array.from({ length: days }, (_, i) => ({
    day: i + 1,
    date: new Date(Date.now() + i * 24 * 60 * 60 * 1000).toDateString(),
    temp: current.temp + Math.floor(Math.random() * 10 - 5),
    condition: current.condition,
  }));
  
  return object({ city, forecast });
});

// RESOURCES
server.resource({
  name: "supported-cities",
  uri: "weather://cities",
  title: "Supported Cities",
}, async () => object({
  cities: Object.keys(weatherData).map(c => c.charAt(0).toUpperCase() + c.slice(1)),
  count: Object.keys(weatherData).length,
}));

server.resource({
  name: "city-info",
  uri: "weather://cities/{city}",
  title: "City Weather Info",
}, async ({ city }) => {
  const weather = weatherData[city.toLowerCase()];
  if (!weather) return text(`No data for ${city}`);
  
  return markdown(`
# Weather in ${city}
- **Temperature**: ${weather.temp}°F
- **Condition**: ${weather.condition}
- **Humidity**: ${weather.humidity}%
  `);
});

// PROMPTS
server.prompt({
  name: "weather-report",
  description: "Generate weather report",
  schema: z.object({
    city: z.string(),
    includeAdvice: z.boolean().default(true),
  }),
}, async ({ city, includeAdvice }) => text(`
Generate a weather report for ${city}...
`));

await server.listen(3000);
console.log("✅ Weather Server running!");
console.log("🔍 Inspector: http://localhost:3000/inspector");

Next Steps

Add Widgets

Create interactive UI widgets for your server

Connect AI Agents

Build agents that use your server’s tools

Deploy to Production

Deploy your server to Manufact MCP Cloud

Examples

Explore more complex examples

Troubleshooting

Make sure:
  • Server is running on the correct port
  • No firewall blocking the connection
  • Check browser console for errors
Verify:
  • Tool registration syntax is correct
  • Server restarted after changes
  • No syntax errors in tool definitions
  • Check Zod schema matches your data
  • Ensure required fields are provided
  • Validate enums and constraints

Build docs developers (and LLMs) love