mcpcraft-sdk
Examples

API Fetch Tool

Build a production-ready MCP tool that fetches live data from external APIs with error handling and input validation.

API Fetch Tool

This example shows how to build robust tools that fetch real-time data from external APIs — with proper error handling, input validation, and safe response parsing.

import { createServer, tool } from "mcpcraft-sdk"

const server = createServer({ name: "api-fetch-server" })

// ── GitHub user fetch ──
server.add(tool({
  name: "get_github_user",
  description: "Fetches public GitHub profile data for a user",
  input: {
    username: { type: "string", description: "GitHub username" }
  },
  run: async ({ username }) => {
    try {
      const user = (username ?? "").trim()
      if (!user) return { error: "Username is required" }

      const res = await fetch(`https://api.github.com/users/${encodeURIComponent(user)}`, {
        headers: { "User-Agent": "mcpcraft-sdk/1.0" }
      })

      if (res.status === 403) return { error: "API rate limited. Try again later." }
      if (res.status === 404) return { error: `User "${user}" not found` }
      if (!res.ok) return { error: `GitHub API error: ${res.status}` }

      const data = await res.json()
      return {
        login: data.login ?? null,
        name: data.name ?? null,
        public_repos: data.public_repos ?? 0,
        followers: data.followers ?? 0,
        bio: data.bio ?? null
      }
    } catch (err) {
      return { error: `Failed to fetch GitHub user: ${err instanceof Error ? err.message : "Unknown error"}` }
    }
  }
}))

// ── Weather fetch ──
server.add(tool({
  name: "get_weather",
  description: "Fetches current weather for a city",
  input: {
    city: { type: "string", description: "City name" }
  },
  run: async ({ city }) => {
    try {
      const c = (city ?? "").trim()
      if (!c) return { error: "City name is required" }

      const geoRes = await fetch(
        `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(c)}&count=1&format=json`
      )
      if (!geoRes.ok) return { error: `Geocoding API error: ${geoRes.status}` }

      const geoData = await geoRes.json()
      const result = geoData.results?.[0]
      if (!result) return { error: `City "${c}" not found` }

      const { latitude, longitude, name, country } = result
      const weatherRes = await fetch(
        `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true`
      )
      if (!weatherRes.ok) return { error: `Weather API error: ${weatherRes.status}` }

      const weatherData = await weatherRes.json()
      const w = weatherData.current_weather
      if (!w) return { error: "Weather data unavailable" }

      return {
        location: `${name ?? c}${country ? `, ${country}` : ""}`,
        temperature: w.temperature != null ? `${w.temperature}°C` : null,
        windspeed: w.windspeed != null ? `${w.windspeed} km/h` : null,
        condition_code: w.weathercode ?? null
      }
    } catch (err) {
      return { error: `Failed to fetch weather: ${err instanceof Error ? err.message : "Unknown error"}` }
    }
  }
}))

server.start()

Key improvements

PatternWhy
try/catchPrevents unhandled rejections from crashing the server
Input trimming + empty checkRejects blank input before making network calls
encodeURIComponentSanitizes user input in URL paths
User-Agent headerRequired by GitHub API; prevents 403 blocks
Status-specific messagesDistinguishes rate limits, not-found, and generic errors
Optional chaining (?.)Safely accesses nested fields without crashing on undefined
err instanceof ErrorAvoids exposing raw exception internals to the LLM

Running It

npx ts-node api-fetch.ts

Connect to any MCP client. Ask "What's the weather in Tokyo?" or "Show me vercel's GitHub profile" — the LLM calls the tool and returns live, structured data.