Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · mcp

Build an MCP server changeddocs/draft/develop/build-server

Upstream edited this page at 12 Oct 2026 16:26 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 12 Oct 2026 16:37 UTC.

Upstream edited
Recorded here
Lines+161added
Lines−154removed
From line 454 where the diff opens
First seen 26 Aug 2026 this site's first read of the page
Recorded edits4to this page, all time

The whole hunk

from line 454, old and new numbered
/
lines
from line 454
454454 
455455 ## Building your server
456456 
457 ### Importing packages and setting up the instance
457 ### Importing packages and setting up constants
458458 
459459 Add these to the top of your `src/index.ts`:
460460 
461461 ```typescript theme={null}
462462 import { McpServer } from "@modelcontextprotocol/server";
463 import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
463 import { serveStdio } from "@modelcontextprotocol/server/stdio";
464464 import { z } from "zod";
465465 
466466 const NWS_API_BASE = "https://api.weather.gov";
467467 const USER_AGENT = "weather-app/1.0";
468 
469 // Create server instance
470 const server = new McpServer({
471 name: "weather",
472 version: "1.0.0",
473 });
474468 ```
475469 
476470 ### Helper functions
from line 542
548542 
549543 ### Implementing tool execution
550544 
551 The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
545 The tool execution handler is responsible for actually executing the logic of each tool. Let's add it, inside a `createServer()` function that builds the server instance and registers both tools on it:
552546 
553547 ```typescript theme={null}
554 // Register weather tools
548 // Build a server instance and register its tools. serveStdio calls this
549 // whenever it needs an instance, so every call must return a fresh one.
550 function createServer() {
551 const server = new McpServer({
552 name: "weather",
553 version: "1.0.0",
554 });
555555 
556 server.registerTool(
557 "get_alerts",
558 {
559 description: "Get weather alerts for a state",
560 inputSchema: z.object({
561 state: z
562 .string()
563 .length(2)
564 .describe("Two-letter state code (e.g. CA, NY)"),
565 }),
566 },
567 async ({ state }) => {
568 const stateCode = state.toUpperCase();
569 const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
570 const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
556 server.registerTool(
557 "get_alerts",
558 {
559 description: "Get weather alerts for a state",
560 inputSchema: z.object({
561 state: z
562 .string()
563 .length(2)
564 .describe("Two-letter state code (e.g. CA, NY)"),
565 }),
566 },
567 async ({ state }) => {
568 const stateCode = state.toUpperCase();
569 const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
570 const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
571571 
572 if (!alertsData) {
573 return {
574 content: [
575 {
576 type: "text",
577 text: "Failed to retrieve alerts data",
578 },
579 ],
580 };
581 }
572 if (!alertsData) {
573 return {
574 content: [
575 {
576 type: "text",
577 text: "Failed to retrieve alerts data",
578 },
579 ],
580 };
581 }
582582 
583 const features = alertsData.features || [];
584 if (!features.length) {
583 const features = alertsData.features || [];
584 if (!features.length) {
585 return {
586 content: [
587 {
588 type: "text",
589 text: `No active alerts for ${stateCode}`,
590 },
591 ],
592 };
593 }
594 
595 const formattedAlerts = features.map(formatAlert);
596 const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
597 
585598 return {
586599 content: [
587600 {
588601 type: "text",
589 text: `No active alerts for ${stateCode}`,
602 text: alertsText,
590603 },
591604 ],
592605 };
593 }
606 },
607 );
594608 
595 const formattedAlerts = features.map(formatAlert);
596 const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
609 server.registerTool(
610 "get_forecast",
611 {
612 description: "Get weather forecast for a location",
613 inputSchema: z.object({
614 latitude: z
615 .number()
616 .min(-90)
617 .max(90)
618 .describe("Latitude of the location"),
619 longitude: z
620 .number()
621 .min(-180)
622 .max(180)
623 .describe("Longitude of the location"),
624 }),
625 },
626 async ({ latitude, longitude }) => {
627 // Get grid point data
628 const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
629 const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
597630 
598 return {
599 content: [
600 {
601 type: "text",
602 text: alertsText,
603 },
604 ],
605 };
606 },
607 );
631 if (!pointsData) {
632 return {
633 content: [
634 {
635 type: "text",
636 text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
637 },
638 ],
639 };
640 }
608641 
609 server.registerTool(
610 "get_forecast",
611 {
612 description: "Get weather forecast for a location",
613 inputSchema: z.object({
614 latitude: z
615 .number()
616 .min(-90)
617 .max(90)
618 .describe("Latitude of the location"),
619 longitude: z
620 .number()
621 .min(-180)
622 .max(180)
623 .describe("Longitude of the location"),
624 }),
625 },
626 async ({ latitude, longitude }) => {
627 // Get grid point data
628 const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
629 const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
642 const forecastUrl = pointsData.properties?.forecast;
643 if (!forecastUrl) {
644 return {
645 content: [
646 {
647 type: "text",
648 text: "Failed to get forecast URL from grid point data",
649 },
650 ],
651 };
652 }
630653 
631 if (!pointsData) {
632 return {
633 content: [
634 {
635 type: "text",
636 text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
637 },
638 ],
639 };
640 }
654 // Get forecast data
655 const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
656 if (!forecastData) {
657 return {
658 content: [
659 {
660 type: "text",
661 text: "Failed to retrieve forecast data",
662 },
663 ],
664 };
665 }
641666 
642 const forecastUrl = pointsData.properties?.forecast;
643 if (!forecastUrl) {
644 return {
645 content: [
646 {
647 type: "text",
648 text: "Failed to get forecast URL from grid point data",
649 },
650 ],
651 };
652 }
667 const periods = forecastData.properties?.periods || [];
668 if (periods.length === 0) {
669 return {
670 content: [
671 {
672 type: "text",
673 text: "No forecast periods available",
674 },
675 ],
676 };
677 }
653678 
654 // Get forecast data
655 const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
656 if (!forecastData) {
657 return {
658 content: [
659 {
660 type: "text",
661 text: "Failed to retrieve forecast data",
662 },
663 ],
664 };
665 }
679 // Format forecast periods
680 const formattedForecast = periods.map((period: ForecastPeriod) =>
681 [
682 `${period.name || "Unknown"}:`,
683 `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
684 `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
685 `${period.shortForecast || "No forecast available"}`,
686 "---",
687 ].join("\n"),
688 );
666689 
667 const periods = forecastData.properties?.periods || [];
668 if (periods.length === 0) {
690 const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
691 
669692 return {
670693 content: [
671694 {
672695 type: "text",
673 text: "No forecast periods available",
696 text: forecastText,
674697 },
675698 ],
676699 };
677 }
700 },
701 );
678702 
679 // Format forecast periods
680 const formattedForecast = periods.map((period: ForecastPeriod) =>
681 [
682 `${period.name || "Unknown"}:`,
683 `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
684 `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
685 `${period.shortForecast || "No forecast available"}`,
686 "---",
687 ].join("\n"),
688 );
689 
690 const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
691 
692 return {
693 content: [
694 {
695 type: "text",
696 text: forecastText,
697 },
698 ],
699 };
700 },
701 );
703 return server;
704 }
702705 ```
703706 
704707 ### Running the server
705708 
706 Finally, implement the main function to run the server:
709 Finally, serve the server over stdio:
707710 
708711 ```typescript theme={null}
709 async function main() {
710 const transport = new StdioServerTransport();
711 await server.connect(transport);
712 console.error("Weather MCP Server running on stdio");
713 }
714 
715 main().catch((error) => {
716 console.error("Fatal error in main():", error);
717 process.exit(1);
712 serveStdio(createServer, {
713 onerror: (error) => console.error("Weather MCP Server error:", error),
718714 });
715 console.error("Weather MCP Server running on stdio");
719716 ```
720717 
718 `serveStdio` negotiates the protocol revision with each client, so the same server answers both 2026-07-28 clients and clients that still open with the 2025-era `initialize` handshake. It calls `createServer()` whenever it needs an instance, including once for a `server/discover` probe that is discarded if the client falls back to `initialize`, which is why the factory returns a new `McpServer` on every call. Wiring the server by hand with `server.connect(new StdioServerTransport())` still compiles, but a server started that way only serves 2025-era clients.
719 
721720 Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
721 
722 To check that the server answers 2026-07-28 clients, list its tools with the [Inspector CLI](/docs/draft/tools/inspector/cli) in the `modern` [protocol era](/docs/draft/tools/inspector/protocol-eras):
723 
724 ```bash theme={null}
725 npx @modelcontextprotocol/inspector --cli node build/index.js --protocol-era modern --method tools/list
726 ```
727 
728 The Inspector defaults to the `legacy` era, which only exercises the 2025-era handshake, so run the same command once more with `--protocol-era legacy`. Both runs list `get_alerts` and `get_forecast`.
722729 
723730 Let's now test your server from an existing MCP host, Claude for Desktop.
724731 
Feedback