← Back to Blog
tutorialprojectapi-integration

Building a Weather MCP Tool: Complete Walkthrough

Build a real weather MCP tool from scratch — API integration, error handling, testing, and publishing to mcpm.

·4 min read·xapable

Building a Weather MCP Tool: Complete Walkthrough

Build a real, working weather MCP tool — from API key to mcpm publish.

What You'll Build

A weather MCP server with:

  • get_current_weather — current conditions for any city
  • get_forecast — 5-day forecast
  • Proper error handling
  • Unit conversion (celsius/fahrenheit)

Step 1: Get an API Key

Sign up for a free OpenWeatherMap API key at https://openweathermap.org/api.

Free tier: 60 calls/minute, plenty for personal use.

Step 2: Initialize

<span class="hljs-built_in">mkdir</span> weather-mcp &amp;&amp; <span class="hljs-built_in">cd</span> weather-mcp
npm init -y
npm install @modelcontextprotocol/sdk

Step 3: Write the Server

Create index.js:

<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Server</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;@modelcontextprotocol/sdk/server/index.js&quot;</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">StdioServerTransport</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;@modelcontextprotocol/sdk/server/stdio.js&quot;</span>;

<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">API_KEY</span> = process.<span class="hljs-property">env</span>.<span class="hljs-property">OPENWEATHER_API_KEY</span>;
<span class="hljs-keyword">if</span> (!<span class="hljs-variable constant_">API_KEY</span>) {
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&quot;OPENWEATHER_API_KEY environment variable is required&quot;</span>);
  process.<span class="hljs-title function_">exit</span>(<span class="hljs-number">1</span>);
}

<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Server</span>({
  <span class="hljs-attr">name</span>: <span class="hljs-string">&quot;weather-mcp&quot;</span>,
  <span class="hljs-attr">version</span>: <span class="hljs-string">&quot;1.0.0&quot;</span>,
}, {
  <span class="hljs-attr">capabilities</span>: { <span class="hljs-attr">tools</span>: {} }
});

server.<span class="hljs-title function_">setRequestHandler</span>(<span class="hljs-string">&quot;tools/list&quot;</span>, <span class="hljs-title function_">async</span> () =&gt; ({
  <span class="hljs-attr">tools</span>: [
    {
      <span class="hljs-attr">name</span>: <span class="hljs-string">&quot;get_current_weather&quot;</span>,
      <span class="hljs-attr">description</span>: <span class="hljs-string">&quot;Get current weather conditions for any city&quot;</span>,
      <span class="hljs-attr">inputSchema</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;object&quot;</span>,
        <span class="hljs-attr">properties</span>: {
          <span class="hljs-attr">city</span>: {
            <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;string&quot;</span>,
            <span class="hljs-attr">description</span>: <span class="hljs-string">&quot;City name (e.g., &#x27;London&#x27;, &#x27;Tokyo&#x27;, &#x27;San Francisco&#x27;)&quot;</span>
          },
          <span class="hljs-attr">units</span>: {
            <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;string&quot;</span>,
            <span class="hljs-attr">enum</span>: [<span class="hljs-string">&quot;celsius&quot;</span>, <span class="hljs-string">&quot;fahrenheit&quot;</span>],
            <span class="hljs-attr">description</span>: <span class="hljs-string">&quot;Temperature unit (default: celsius)&quot;</span>
          }
        },
        <span class="hljs-attr">required</span>: [<span class="hljs-string">&quot;city&quot;</span>]
      }
    },
    {
      <span class="hljs-attr">name</span>: <span class="hljs-string">&quot;get_forecast&quot;</span>,
      <span class="hljs-attr">description</span>: <span class="hljs-string">&quot;Get 5-day weather forecast for any city&quot;</span>,
      <span class="hljs-attr">inputSchema</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;object&quot;</span>,
        <span class="hljs-attr">properties</span>: {
          <span class="hljs-attr">city</span>: {
            <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;string&quot;</span>,
            <span class="hljs-attr">description</span>: <span class="hljs-string">&quot;City name&quot;</span>
          },
          <span class="hljs-attr">days</span>: {
            <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;number&quot;</span>,
            <span class="hljs-attr">description</span>: <span class="hljs-string">&quot;Number of days to forecast (1-5, default: 3)&quot;</span>
          }
        },
        <span class="hljs-attr">required</span>: [<span class="hljs-string">&quot;city&quot;</span>]
      }
    }
  ]
}));

server.<span class="hljs-title function_">setRequestHandler</span>(<span class="hljs-string">&quot;tools/call&quot;</span>, <span class="hljs-title function_">async</span> (request) =&gt; {
  <span class="hljs-keyword">const</span> { name, <span class="hljs-attr">arguments</span>: args } = request.<span class="hljs-property">params</span>;

  <span class="hljs-keyword">switch</span> (name) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">&quot;get_current_weather&quot;</span>: {
      <span class="hljs-keyword">const</span> { city, units = <span class="hljs-string">&quot;celsius&quot;</span> } = args;
      <span class="hljs-keyword">const</span> unitParam = units === <span class="hljs-string">&quot;fahrenheit&quot;</span> ? <span class="hljs-string">&quot;imperial&quot;</span> : <span class="hljs-string">&quot;metric&quot;</span>;

      <span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(
        <span class="hljs-string">`https://api.openweathermap.org/data/2.5/weather?q=<span class="hljs-subst">${<span class="hljs-built_in">encodeURIComponent</span>(city)}</span>&amp;units=<span class="hljs-subst">${unitParam}</span>&amp;appid=<span class="hljs-subst">${API_KEY}</span>`</span>
      );

      <span class="hljs-keyword">if</span> (!resp.<span class="hljs-property">ok</span>) {
        <span class="hljs-keyword">const</span> err = <span class="hljs-keyword">await</span> resp.<span class="hljs-title function_">json</span>();
        <span class="hljs-keyword">return</span> {
          <span class="hljs-attr">content</span>: [{ <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;text&quot;</span>, <span class="hljs-attr">text</span>: <span class="hljs-string">`❌ Weather not found: <span class="hljs-subst">${err.message || city}</span>`</span> }]
        };
      }

      <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> resp.<span class="hljs-title function_">json</span>();
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">content</span>: [{
          <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;text&quot;</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">`🌤️ <span class="hljs-subst">${data.name}</span>, <span class="hljs-subst">${data.sys.country}</span>
`</span> +
                <span class="hljs-string">`   Temperature: <span class="hljs-subst">${<span class="hljs-built_in">Math</span>.round(data.main.temp)}</span>°<span class="hljs-subst">${units === <span class="hljs-string">&quot;fahrenheit&quot;</span> ? <span class="hljs-string">&quot;F&quot;</span> : <span class="hljs-string">&quot;C&quot;</span>}</span>
`</span> +
                <span class="hljs-string">`   Feels like: <span class="hljs-subst">${<span class="hljs-built_in">Math</span>.round(data.main.feels_like)}</span>°
`</span> +
                <span class="hljs-string">`   Condition: <span class="hljs-subst">${data.weather[<span class="hljs-number">0</span>].description}</span>
`</span> +
                <span class="hljs-string">`   Humidity: <span class="hljs-subst">${data.main.humidity}</span>%
`</span> +
                <span class="hljs-string">`   Wind: <span class="hljs-subst">${data.wind.speed}</span> m/s`</span>
        }]
      };
    }

    <span class="hljs-keyword">case</span> <span class="hljs-string">&quot;get_forecast&quot;</span>: {
      <span class="hljs-keyword">const</span> { city, days = <span class="hljs-number">3</span> } = args;

      <span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(
        <span class="hljs-string">`https://api.openweathermap.org/data/2.5/forecast?q=<span class="hljs-subst">${<span class="hljs-built_in">encodeURIComponent</span>(city)}</span>&amp;units=metric&amp;appid=<span class="hljs-subst">${API_KEY}</span>`</span>
      );

      <span class="hljs-keyword">if</span> (!resp.<span class="hljs-property">ok</span>) {
        <span class="hljs-keyword">return</span> {
          <span class="hljs-attr">content</span>: [{ <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;text&quot;</span>, <span class="hljs-attr">text</span>: <span class="hljs-string">`❌ Forecast not available for: <span class="hljs-subst">${city}</span>`</span> }]
        };
      }

      <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> resp.<span class="hljs-title function_">json</span>();
      <span class="hljs-keyword">const</span> daily = {};
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> entry <span class="hljs-keyword">of</span> data.<span class="hljs-property">list</span>) {
        <span class="hljs-keyword">const</span> date = entry.<span class="hljs-property">dt_txt</span>.<span class="hljs-title function_">split</span>(<span class="hljs-string">&quot; &quot;</span>)[<span class="hljs-number">0</span>];
        <span class="hljs-keyword">if</span> (!daily[date]) {
          daily[date] = { <span class="hljs-attr">temps</span>: [], <span class="hljs-attr">conditions</span>: [] };
        }
        daily[date].<span class="hljs-property">temps</span>.<span class="hljs-title function_">push</span>(entry.<span class="hljs-property">main</span>.<span class="hljs-property">temp</span>);
        daily[date].<span class="hljs-property">conditions</span>.<span class="hljs-title function_">push</span>(entry.<span class="hljs-property">weather</span>[<span class="hljs-number">0</span>].<span class="hljs-property">description</span>);
      }

      <span class="hljs-keyword">const</span> forecast = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">entries</span>(daily).<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, days).<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">[date, d]</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> avg = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">round</span>(d.<span class="hljs-property">temps</span>.<span class="hljs-title function_">reduce</span>(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> a + b, <span class="hljs-number">0</span>) / d.<span class="hljs-property">temps</span>.<span class="hljs-property">length</span>);
        <span class="hljs-keyword">const</span> mostCommon = d.<span class="hljs-property">conditions</span>.<span class="hljs-title function_">sort</span>(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span>
          d.<span class="hljs-property">conditions</span>.<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> v === a).<span class="hljs-property">length</span> - d.<span class="hljs-property">conditions</span>.<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> v === b).<span class="hljs-property">length</span>
        ).<span class="hljs-title function_">pop</span>();
        <span class="hljs-keyword">return</span> <span class="hljs-string">`   <span class="hljs-subst">${date}</span>: <span class="hljs-subst">${avg}</span>°C, <span class="hljs-subst">${mostCommon}</span>`</span>;
      }).<span class="hljs-title function_">join</span>(<span class="hljs-string">&quot;\n&quot;</span>);

      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">content</span>: [{
          <span class="hljs-attr">type</span>: <span class="hljs-string">&quot;text&quot;</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">`📅 <span class="hljs-subst">${days}</span>-Day Forecast for <span class="hljs-subst">${data.city.name}</span>:
<span class="hljs-subst">${forecast}</span>`</span>
        }]
      };
    }

    <span class="hljs-attr">default</span>:
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">`Unknown tool: <span class="hljs-subst">${name}</span>`</span>);
  }
});

<span class="hljs-keyword">const</span> transport = <span class="hljs-keyword">new</span> <span class="hljs-title class_">StdioServerTransport</span>();
<span class="hljs-keyword">await</span> server.<span class="hljs-title function_">connect</span>(transport);

Step 4: Test Locally

OPENWEATHER_API_KEY=your_key node index.js

Step 5: Write README.md

<span class="hljs-section"># Weather MCP</span>

Get real-time weather and forecasts via MCP.

<span class="hljs-section">## Install</span>
\<span class="hljs-code">`\`</span>\`bash
mcpm-dev add weather-mcp
\<span class="hljs-code">`\`</span>\`

<span class="hljs-section">## Setup</span>
Get a free API key at https://openweathermap.org/api

<span class="hljs-section">## Tools</span>
See full documentation at https://www.mcpm.dev/packages/weather-mcp

Step 6: Publish

mcpm-dev login
mcpm-dev publish

Key Takeaways

  1. API keys in env vars — never hardcode
  2. Handle errors gracefully — agents need clear messages
  3. Encode user inputencodeURIComponent() is essential
  4. Format output for readability — use emoji and newlines

Your weather MCP tool is live! 🎉

#MCP #Tutorial #Weather #mcpm #NodeJS