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}¤t_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
| Pattern | Why |
|---|---|
try/catch | Prevents unhandled rejections from crashing the server |
| Input trimming + empty check | Rejects blank input before making network calls |
encodeURIComponent | Sanitizes user input in URL paths |
User-Agent header | Required by GitHub API; prevents 403 blocks |
| Status-specific messages | Distinguishes rate limits, not-found, and generic errors |
Optional chaining (?.) | Safely accesses nested fields without crashing on undefined |
err instanceof Error | Avoids exposing raw exception internals to the LLM |
Running It
npx ts-node api-fetch.tsConnect 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.