docs: correctness and coverage pass for the 2026-07-28 revision (#2389)
This commit is contained in:
parent
36055d5bbd
commit
199a28e9e2
1 changed files with 1335 additions and 390 deletions
|
|
@ -88,13 +88,9 @@ Update your `package.json` to add `type: "module"` and a build script:
|
|||
```json
|
||||
{
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"weather": "./build/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && chmod 755 build/index.js"
|
||||
},
|
||||
"files": ["build"]
|
||||
"build": "tsc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -129,17 +125,11 @@ Add these to the top of your `src/index.ts`:
|
|||
|
||||
```ts source="../examples/server-quickstart/src/index.ts#prelude"
|
||||
import { McpServer } from '@modelcontextprotocol/server';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
|
||||
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
||||
import * as z from 'zod/v4';
|
||||
|
||||
const NWS_API_BASE = 'https://api.weather.gov';
|
||||
const USER_AGENT = 'weather-app/1.0';
|
||||
|
||||
// Create server instance
|
||||
const server = new McpServer({
|
||||
name: 'weather',
|
||||
version: '1.0.0',
|
||||
});
|
||||
```
|
||||
|
||||
### Helper functions
|
||||
|
|
@ -217,149 +207,156 @@ interface ForecastResponse {
|
|||
|
||||
### Registering tools
|
||||
|
||||
Each tool is registered with {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | server.registerTool()}, which takes the tool name, a configuration object (with description and input schema), and a callback that implements the tool logic. Let's register our two weather tools:
|
||||
Each tool is registered with {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | server.registerTool()}, which takes the tool name, a configuration object (with description and input schema), and a callback that implements the tool logic. Create the server inside a `createServer()` factory and register both weather tools on it. The serving entry in the next step builds the instance it serves by calling this factory, so keep it cheap and side-effect-free:
|
||||
|
||||
```ts source="../examples/server-quickstart/src/index.ts#registerTools"
|
||||
// Register weather tools
|
||||
server.registerTool(
|
||||
'get-alerts',
|
||||
{
|
||||
title: 'Get Weather Alerts',
|
||||
description: 'Get weather alerts for a state',
|
||||
inputSchema: z.object({
|
||||
state: z.string().length(2)
|
||||
.describe('Two-letter state code (e.g. CA, NY)'),
|
||||
}),
|
||||
},
|
||||
async ({ state }) => {
|
||||
const stateCode = state.toUpperCase();
|
||||
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
|
||||
const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
|
||||
// Create a server with both weather tools registered. The serving entry calls
|
||||
// this factory to build the instance it serves, so keep it cheap and
|
||||
// side-effect-free.
|
||||
function createServer(): McpServer {
|
||||
const server = new McpServer({
|
||||
name: 'weather',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
server.registerTool(
|
||||
'get-alerts',
|
||||
{
|
||||
title: 'Get Weather Alerts',
|
||||
description: 'Get weather alerts for a state',
|
||||
inputSchema: z.object({
|
||||
state: z.string().length(2)
|
||||
.describe('Two-letter state code (e.g. CA, NY)'),
|
||||
}),
|
||||
},
|
||||
async ({ state }) => {
|
||||
const stateCode = state.toUpperCase();
|
||||
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
|
||||
const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
|
||||
|
||||
if (!alertsData) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'Failed to retrieve alerts data',
|
||||
}],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const features = alertsData.features || [];
|
||||
|
||||
if (features.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: `No active alerts for ${stateCode}`,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
const formattedAlerts = features.map(formatAlert);
|
||||
|
||||
if (!alertsData) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: 'Failed to retrieve alerts data',
|
||||
type: 'text',
|
||||
text: `Active alerts for ${stateCode}:\n\n${formattedAlerts.join('\n')}`,
|
||||
}],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const features = alertsData.features || [];
|
||||
server.registerTool(
|
||||
'get-forecast',
|
||||
{
|
||||
title: 'Get Weather Forecast',
|
||||
description: 'Get weather forecast for a location',
|
||||
inputSchema: z.object({
|
||||
latitude: z.number().min(-90).max(90)
|
||||
.describe('Latitude of the location'),
|
||||
longitude: z.number().min(-180).max(180)
|
||||
.describe('Longitude of the location'),
|
||||
}),
|
||||
},
|
||||
async ({ latitude, longitude }) => {
|
||||
// Get grid point data
|
||||
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
|
||||
const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
|
||||
|
||||
if (!pointsData) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
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).`,
|
||||
}],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const forecastUrl = pointsData.properties?.forecast;
|
||||
if (!forecastUrl) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'Failed to get forecast URL from grid point data',
|
||||
}],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Get forecast data
|
||||
const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
|
||||
if (!forecastData) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'Failed to retrieve forecast data',
|
||||
}],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const periods = forecastData.properties?.periods || [];
|
||||
if (periods.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'No forecast periods available',
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
// Format forecast periods
|
||||
const formattedForecast = periods.map((period: ForecastPeriod) =>
|
||||
[
|
||||
`${period.name || 'Unknown'}:`,
|
||||
`Temperature: ${period.temperature || 'Unknown'}°${period.temperatureUnit || 'F'}`,
|
||||
`Wind: ${period.windSpeed || 'Unknown'} ${period.windDirection || ''}`,
|
||||
`${period.shortForecast || 'No forecast available'}`,
|
||||
'---',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
if (features.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No active alerts for ${stateCode}`,
|
||||
type: 'text',
|
||||
text: `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join('\n')}`,
|
||||
}],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const formattedAlerts = features.map(formatAlert);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Active alerts for ${stateCode}:\n\n${formattedAlerts.join('\n')}`,
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'get-forecast',
|
||||
{
|
||||
title: 'Get Weather Forecast',
|
||||
description: 'Get weather forecast for a location',
|
||||
inputSchema: z.object({
|
||||
latitude: z.number().min(-90).max(90)
|
||||
.describe('Latitude of the location'),
|
||||
longitude: z.number().min(-180).max(180)
|
||||
.describe('Longitude of the location'),
|
||||
}),
|
||||
},
|
||||
async ({ latitude, longitude }) => {
|
||||
// Get grid point data
|
||||
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
|
||||
const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
|
||||
|
||||
if (!pointsData) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
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).`,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
const forecastUrl = pointsData.properties?.forecast;
|
||||
if (!forecastUrl) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: 'Failed to get forecast URL from grid point data',
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
// Get forecast data
|
||||
const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
|
||||
if (!forecastData) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: 'Failed to retrieve forecast data',
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
const periods = forecastData.properties?.periods || [];
|
||||
if (periods.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: 'No forecast periods available',
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
// Format forecast periods
|
||||
const formattedForecast = periods.map((period: ForecastPeriod) =>
|
||||
[
|
||||
`${period.name || 'Unknown'}:`,
|
||||
`Temperature: ${period.temperature || 'Unknown'}°${period.temperatureUnit || 'F'}`,
|
||||
`Wind: ${period.windSpeed || 'Unknown'} ${period.windDirection || ''}`,
|
||||
`${period.shortForecast || 'No forecast available'}`,
|
||||
'---',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join('\n')}`,
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
return server;
|
||||
}
|
||||
```
|
||||
|
||||
### Running the server
|
||||
|
||||
Finally, implement the main function to run the server:
|
||||
Finally, serve the factory on stdio with {@linkcode @modelcontextprotocol/server!server/serveStdio.serveStdio | serveStdio}. The entry owns the transport and negotiates the protocol revision with each client, so the same factory serves current hosts (such as VS Code) and clients that speak the 2026-07-28 draft revision; see [Serving the 2026-07-28 draft revision on stdio](./server.md#serving-the-2026-07-28-draft-revision-on-stdio) in the server guide for the options:
|
||||
|
||||
```ts source="../examples/server-quickstart/src/index.ts#main"
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('Weather MCP Server running on stdio');
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error in main():', error);
|
||||
process.exit(1);
|
||||
});
|
||||
void serveStdio(createServer);
|
||||
console.error('Weather MCP Server running on stdio');
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
|
|
|
|||
Loading…
Reference in a new issue