Merge pull request #7 from modelcontextprotocol/justin/update-typescript-sdk
Update TS quickstart to use simplified API
This commit is contained in:
commit
7ff288959d
3 changed files with 164 additions and 198 deletions
53
weather-server-typescript/package-lock.json
generated
53
weather-server-typescript/package-lock.json
generated
|
|
@ -9,7 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.0.3"
|
"@modelcontextprotocol/sdk": "^1.4.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"weather": "build/index.js"
|
"weather": "build/index.js"
|
||||||
|
|
@ -20,13 +20,19 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@modelcontextprotocol/sdk": {
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "1.0.3",
|
"version": "1.4.0",
|
||||||
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@modelcontextprotocol/sdk/-/sdk-1.0.3.tgz",
|
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@modelcontextprotocol/sdk/-/sdk-1.4.0.tgz",
|
||||||
"integrity": "sha512-2as3cX/VJ0YBHGmdv3GFyTpoM8q2gqE98zh3Vf1NwnsSY0h3mvoO07MUzfygCKkWsFjcZm4otIiqD6Xh7kiSBQ==",
|
"integrity": "sha512-50YTsT4H9PuqmgwXkr/BPl0ankfEfxqVtFG8I378XqUThasnYRdNwE2PqktiJNm5MncOf3s5q37juv6kMe6WZQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
|
"eventsource": "^3.0.2",
|
||||||
"raw-body": "^3.0.0",
|
"raw-body": "^3.0.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8",
|
||||||
|
"zod-to-json-schema": "^3.24.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
|
|
@ -62,6 +68,27 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventsource": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/eventsource/-/eventsource-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-YolzkJNxsTL3tCJMWFxpxtG2sCjbZ4LQUBUrkdaJK0ub0p6lmJt+2+1SwhKjLc652lpH9L/79Ptez972H9tphw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"eventsource-parser": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eventsource-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/eventsource-parser/-/eventsource-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/http-errors/2.0.0/http-errors-2.0.0.tgz",
|
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/http-errors/2.0.0/http-errors-2.0.0.tgz",
|
||||||
|
|
@ -161,12 +188,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.23.8",
|
"version": "3.24.1",
|
||||||
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/zod/3.23.8/zod-3.23.8.tgz",
|
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/zod/-/zod-3.24.1.tgz",
|
||||||
"integrity": "sha1-43uVe11SB5dp+4CXCZtZLw70Bn0=",
|
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod-to-json-schema": {
|
||||||
|
"version": "3.24.1",
|
||||||
|
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz",
|
||||||
|
"integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.24.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,6 @@
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.0.3"
|
"@modelcontextprotocol/sdk": "^1.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,10 @@
|
||||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
import {
|
|
||||||
CallToolRequestSchema,
|
|
||||||
ListToolsRequestSchema,
|
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const NWS_API_BASE = "https://api.weather.gov";
|
const NWS_API_BASE = "https://api.weather.gov";
|
||||||
const USER_AGENT = "weather-app/1.0";
|
const USER_AGENT = "weather-app/1.0";
|
||||||
|
|
||||||
// Define Zod schemas for validation
|
|
||||||
const AlertsArgumentsSchema = z.object({
|
|
||||||
state: z.string().length(2),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ForecastArgumentsSchema = z.object({
|
|
||||||
latitude: z.number().min(-90).max(90),
|
|
||||||
longitude: z.number().min(-180).max(180),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create server instance
|
|
||||||
const server = new Server(
|
|
||||||
{
|
|
||||||
name: "weather",
|
|
||||||
version: "1.0.0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
capabilities: {
|
|
||||||
tools: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// List available tools
|
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
||||||
return {
|
|
||||||
tools: [
|
|
||||||
{
|
|
||||||
name: "get-alerts",
|
|
||||||
description: "Get weather alerts for a state",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
state: {
|
|
||||||
type: "string",
|
|
||||||
description: "Two-letter state code (e.g. CA, NY)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["state"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get-forecast",
|
|
||||||
description: "Get weather forecast for a location",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
latitude: {
|
|
||||||
type: "number",
|
|
||||||
description: "Latitude of the location",
|
|
||||||
},
|
|
||||||
longitude: {
|
|
||||||
type: "number",
|
|
||||||
description: "Longitude of the location",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["latitude", "longitude"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Helper function for making NWS API requests
|
// Helper function for making NWS API requests
|
||||||
async function makeNWSRequest<T>(url: string): Promise<T | null> {
|
async function makeNWSRequest<T>(url: string): Promise<T | null> {
|
||||||
const headers = {
|
const headers = {
|
||||||
|
|
@ -139,153 +72,149 @@ interface ForecastResponse {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tool execution
|
// Create server instance
|
||||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
const server = new McpServer({
|
||||||
const { name, arguments: args } = request.params;
|
name: "weather",
|
||||||
|
version: "1.0.0",
|
||||||
try {
|
|
||||||
if (name === "get-alerts") {
|
|
||||||
const { state } = AlertsArgumentsSchema.parse(args);
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const features = alertsData.features || [];
|
|
||||||
if (features.length === 0) {
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `No active alerts for ${stateCode}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedAlerts = features.map(formatAlert);
|
|
||||||
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join(
|
|
||||||
"\n"
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: alertsText,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} else if (name === "get-forecast") {
|
|
||||||
const { latitude, longitude } = ForecastArgumentsSchema.parse(args);
|
|
||||||
|
|
||||||
// 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).`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const forecastUrl = pointsData.properties?.forecast;
|
|
||||||
if (!forecastUrl) {
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
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",
|
|
||||||
text: "Failed to retrieve forecast data",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
);
|
|
||||||
|
|
||||||
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join(
|
|
||||||
"\n"
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: forecastText,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unknown tool: ${name}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof z.ZodError) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid arguments: ${error.errors
|
|
||||||
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
||||||
.join(", ")}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register weather tools
|
||||||
|
server.tool(
|
||||||
|
"get-alerts",
|
||||||
|
"Get weather alerts for a state",
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const features = alertsData.features || [];
|
||||||
|
if (features.length === 0) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `No active alerts for ${stateCode}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedAlerts = features.map(formatAlert);
|
||||||
|
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: alertsText,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
"get-forecast",
|
||||||
|
"Get weather forecast for a location",
|
||||||
|
{
|
||||||
|
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).`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const forecastUrl = pointsData.properties?.forecast;
|
||||||
|
if (!forecastUrl) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
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",
|
||||||
|
text: "Failed to retrieve forecast data",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: forecastText,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
async function main() {
|
async function main() {
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue