One read of Model Context Protocolmcp-20261012T163712Z
2 pages moved out of 357 read.
Pages moved
2
significant first
Pages read
357
in this capture
Captured
16:37 UTC
Corpus hash
a55b6b1702a5
corpus-hash
What this read moved
1-2 of 2docs/2026-07-28/develop/build-server Changed · +161 / -154 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/2026-07-28/tools/inspector/cli) in the `modern` [protocol era](/docs/2026-07-28/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
docs/draft/develop/build-server Changed · +161 / -154 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