update examples, update package-lock.json

This commit is contained in:
Basil Hosmer 2025-05-13 16:32:22 -04:00
commit e1abdab7f5
No known key found for this signature in database
7 changed files with 258 additions and 431 deletions

View file

@ -61,19 +61,13 @@ async function main(): Promise<void> {
// Log the results from each tool call
for (const [caller, result] of Object.entries(toolResults)) {
console.log(`\n=== Tool result for ${caller} ===`);
if (result.content) {
result.content.forEach((item: { type: string; text?: string; }) => {
if (item.type === 'text') {
console.log(` ${item.text}`);
} else {
console.log(` ${item.type} content:`, item);
}
});
} else if (result.structuredContent) {
console.log(` Structured content: ${result.structuredContent}`);
} else {
console.log(` No content returned`);
}
result.content?.forEach((item: { type: string; text?: string; }) => {
if (item.type === 'text') {
console.log(` ${item.text}`);
} else {
console.log(` ${item.type} content:`, item);
}
});
}
// 3. Wait for all notifications (10 seconds)

View file

@ -341,19 +341,13 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<vo
});
console.log('Tool result:');
if (result.content) {
result.content.forEach(item => {
if (item.type === 'text') {
console.log(` ${item.text}`);
} else {
console.log(` ${item.type} content:`, item);
}
});
} else if (result.structuredContent) {
console.log(` Structured content: ${result.structuredContent}`);
} else {
console.log(' No content returned');
}
result.content?.forEach(item => {
if (item.type === 'text') {
console.log(` ${item.text}`);
} else {
console.log(` ${item.type} content:`, item);
}
});
} catch (error) {
console.log(`Error calling tool ${name}: ${error}`);
}

View file

@ -173,19 +173,13 @@ async function startNotificationTool(client: Client): Promise<void> {
const result = await client.request(request, CallToolResultSchema);
console.log('Tool result:');
if (result.content) {
result.content.forEach(item => {
if (item.type === 'text') {
console.log(` ${item.text}`);
} else {
console.log(` ${item.type} content:`, item);
}
});
} else if (result.structuredContent) {
console.log(` Structured content: ${result.structuredContent}`);
} else {
console.log(' No content returned');
}
result.content?.forEach(item => {
if (item.type === 'text') {
console.log(` ${item.text}`);
} else {
console.log(` ${item.type} content:`, item);
}
});
} catch (error) {
console.log(`Error calling notification tool: ${error}`);
}

View file

@ -0,0 +1,116 @@
#!/usr/bin/env node
/**
* Example MCP server demonstrating tool outputSchema support using the low-level Server API
* This server manually handles tool listing and invocation requests to return structured data
* For a simpler high-level API approach, see mcpServerOutputSchema.ts
*/
import { Server } from "../../server/index.js";
import { StdioServerTransport } from "../../server/stdio.js";
import { CallToolRequest, CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "../../types.js";
const server = new Server(
{
name: "output-schema-low-level-example",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Tool with structured output
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get_weather",
description: "Get weather information for a city",
inputSchema: {
type: "object",
properties: {
city: { type: "string", description: "City name" },
country: { type: "string", description: "Country code (e.g., US, UK)" }
},
required: ["city", "country"]
},
outputSchema: {
type: "object",
properties: {
temperature: {
type: "object",
properties: {
celsius: { type: "number" },
fahrenheit: { type: "number" }
},
required: ["celsius", "fahrenheit"]
},
conditions: {
type: "string",
enum: ["sunny", "cloudy", "rainy", "stormy", "snowy"]
},
humidity: { type: "number", minimum: 0, maximum: 100 },
wind: {
type: "object",
properties: {
speed_kmh: { type: "number" },
direction: { type: "string" }
},
required: ["speed_kmh", "direction"]
}
},
required: ["temperature", "conditions", "humidity", "wind"]
}
}
]
}));
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
switch (request.params.name) {
case "get_weather": {
const { city, country } = request.params.arguments as { city: string; country: string };
// Parameters are available but not used in this example
void city;
void country;
// Simulate weather API call
const temp_c = Math.round((Math.random() * 35 - 5) * 10) / 10;
const conditions = ["sunny", "cloudy", "rainy", "stormy", "snowy"][Math.floor(Math.random() * 5)];
// Return structured content matching the outputSchema
return {
structuredContent: {
temperature: {
celsius: temp_c,
fahrenheit: Math.round((temp_c * 9/5 + 32) * 10) / 10
},
conditions,
humidity: Math.round(Math.random() * 100),
wind: {
speed_kmh: Math.round(Math.random() * 50),
direction: ["N", "NE", "E", "SE", "S", "SW", "W", "NW"][Math.floor(Math.random() * 8)]
}
}
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Low-level Output Schema Example Server running on stdio");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});

View file

@ -10,47 +10,31 @@ import { z } from "zod";
const server = new McpServer(
{
name: "mcp-output-schema-example",
name: "mcp-output-schema-high-level-example",
version: "1.0.0",
}
);
// Define a tool with structured output - Weather data
server.tool(
"get_weather",
"Get weather information for a city",
{
server.tool("get_weather", {
description: "Get weather information for a city",
inputSchema:{
city: z.string().describe("City name"),
country: z.string().describe("Country code (e.g., US, UK)")
},
{
type: "object",
properties: {
temperature: {
type: "object",
properties: {
celsius: { type: "number" },
fahrenheit: { type: "number" }
},
required: ["celsius", "fahrenheit"]
},
conditions: {
type: "string",
enum: ["sunny", "cloudy", "rainy", "stormy", "snowy"]
},
humidity: { type: "number", minimum: 0, maximum: 100 },
wind: {
type: "object",
properties: {
speed_kmh: { type: "number" },
direction: { type: "string" }
},
required: ["speed_kmh", "direction"]
}
},
required: ["temperature", "conditions", "humidity", "wind"]
outputSchema: {
temperature: z.object({
celsius: z.number(),
fahrenheit: z.number()
}),
conditions: z.enum(["sunny", "cloudy", "rainy", "stormy", "snowy"]),
humidity: z.number().min(0).max(100),
wind: z.object({
speed_kmh: z.number(),
direction: z.string()
})
},
async ({ city, country }: { city: string; country: string }) => {
callback: async ({ city, country }) => {
// Parameters are available but not used in this example
void city;
void country;
@ -73,161 +57,12 @@ server.tool(
}
};
}
);
// Define a tool for data processing with structured output
server.tool(
"process_csv",
"Process CSV data and return statistics",
{
csv_data: z.string().describe("CSV data as a string"),
delimiter: z.string().default(",").describe("CSV delimiter")
},
{
type: "object",
properties: {
row_count: { type: "integer" },
column_count: { type: "integer" },
headers: {
type: "array",
items: { type: "string" }
},
data_types: {
type: "object",
additionalProperties: {
type: "string",
enum: ["number", "string", "date", "boolean"]
}
},
summary: {
type: "object",
additionalProperties: {
type: "object",
properties: {
min: { type: "number" },
max: { type: "number" },
mean: { type: "number" },
count: { type: "integer" }
}
}
}
},
required: ["row_count", "column_count", "headers", "data_types"]
},
async ({ csv_data, delimiter }) => {
const lines = csv_data.trim().split('\n');
const headers = lines[0].split(delimiter).map(h => h.trim());
const data = lines.slice(1).map(line => line.split(delimiter).map(cell => cell.trim()));
// Infer data types
const dataTypes: { [key: string]: string } = {};
const summary: { [key: string]: unknown } = {};
headers.forEach((header, idx) => {
const values = data.map(row => row[idx]);
const numericValues = values.filter(v => !isNaN(Number(v)) && v !== '');
if (numericValues.length === values.length) {
dataTypes[header] = "number";
const numbers = numericValues.map(Number);
summary[header] = {
min: Math.min(...numbers),
max: Math.max(...numbers),
mean: numbers.reduce((a, b) => a + b, 0) / numbers.length,
count: numbers.length
};
} else {
dataTypes[header] = "string";
}
});
return {
structuredContent: {
row_count: data.length,
column_count: headers.length,
headers,
data_types: dataTypes,
summary
}
};
}
);
// Traditional tool without outputSchema for comparison
server.tool(
"echo",
"Echo back the input message",
{
message: z.string()
},
async ({ message }) => {
return {
content: [
{
type: "text",
text: `Echo: ${message}`
}
]
};
}
);
// Tool that can return both structured and unstructured content
server.tool(
"hybrid_tool",
"Tool that returns both structured and readable content",
{
data: z.array(z.number()).describe("Array of numbers to analyze")
},
{
type: "object",
properties: {
stats: {
type: "object",
properties: {
mean: { type: "number" },
median: { type: "number" },
std_dev: { type: "number" }
}
}
},
required: ["stats"]
},
async ({ data }) => {
const mean = data.reduce((a, b) => a + b, 0) / data.length;
const sorted = [...data].sort((a, b) => a - b);
const median = sorted.length % 2 === 0
? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
: sorted[Math.floor(sorted.length / 2)];
const variance = data.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / data.length;
const std_dev = Math.sqrt(variance);
return {
structuredContent: {
stats: {
mean: Math.round(mean * 100) / 100,
median: Math.round(median * 100) / 100,
std_dev: Math.round(std_dev * 100) / 100
}
},
// Also provide human-readable content for backward compatibility
content: [
{
type: "text",
text: `Analysis of ${data.length} numbers:
- Mean: ${Math.round(mean * 100) / 100}
- Median: ${Math.round(median * 100) / 100}
- Standard Deviation: ${Math.round(std_dev * 100) / 100}`
}
]
};
}
);
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("McpServer Output Schema Example running on stdio");
console.error("High-level Output Schema Example Server running on stdio");
}
main().catch((error) => {

View file

@ -1,209 +0,0 @@
#!/usr/bin/env node
/**
* Example MCP server demonstrating tool outputSchema support
* This server exposes tools that return structured data
*/
import { Server } from "../../server/index.js";
import { StdioServerTransport } from "../../server/stdio.js";
import { CallToolRequest, CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "../../types.js";
const server = new Server(
{
name: "output-schema-example",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Tool with structured output
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "calculate_bmi",
description: "Calculate BMI given height and weight",
inputSchema: {
type: "object",
properties: {
height_cm: { type: "number", description: "Height in centimeters" },
weight_kg: { type: "number", description: "Weight in kilograms" }
},
required: ["height_cm", "weight_kg"]
},
outputSchema: {
type: "object",
properties: {
bmi: { type: "number", description: "Body Mass Index" },
category: {
type: "string",
enum: ["underweight", "normal", "overweight", "obese"],
description: "BMI category"
},
healthy_weight_range: {
type: "object",
properties: {
min_kg: { type: "number" },
max_kg: { type: "number" }
},
required: ["min_kg", "max_kg"]
}
},
required: ["bmi", "category", "healthy_weight_range"]
}
},
{
name: "analyze_text",
description: "Analyze text and return structured insights",
inputSchema: {
type: "object",
properties: {
text: { type: "string", description: "Text to analyze" }
},
required: ["text"]
},
outputSchema: {
type: "object",
properties: {
word_count: { type: "integer" },
sentence_count: { type: "integer" },
character_count: { type: "integer" },
reading_time_minutes: { type: "number" },
sentiment: {
type: "string",
enum: ["positive", "negative", "neutral"]
},
key_phrases: {
type: "array",
items: { type: "string" }
}
},
required: ["word_count", "sentence_count", "character_count", "reading_time_minutes"]
}
},
{
name: "traditional_tool",
description: "A traditional tool without outputSchema",
inputSchema: {
type: "object",
properties: {
message: { type: "string" }
},
required: ["message"]
}
}
]
}));
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
switch (request.params.name) {
case "calculate_bmi": {
const { height_cm, weight_kg } = request.params.arguments as { height_cm: number; weight_kg: number };
const height_m = height_cm / 100;
const bmi = weight_kg / (height_m * height_m);
let category: string;
if (bmi < 18.5) category = "underweight";
else if (bmi < 25) category = "normal";
else if (bmi < 30) category = "overweight";
else category = "obese";
// Calculate healthy weight range for normal BMI (18.5-24.9)
const min_healthy_bmi = 18.5;
const max_healthy_bmi = 24.9;
const min_healthy_weight = min_healthy_bmi * height_m * height_m;
const max_healthy_weight = max_healthy_bmi * height_m * height_m;
// Return structured content matching the outputSchema
return {
structuredContent: {
bmi: Math.round(bmi * 10) / 10,
category,
healthy_weight_range: {
min_kg: Math.round(min_healthy_weight * 10) / 10,
max_kg: Math.round(max_healthy_weight * 10) / 10
}
}
};
}
case "analyze_text": {
const { text } = request.params.arguments as { text: string };
// Simple text analysis
const words = text.trim().split(/\s+/);
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
const wordsPerMinute = 200; // Average reading speed
// Very simple sentiment analysis (for demo purposes)
const positiveWords = ["good", "great", "excellent", "happy", "positive", "amazing"];
const negativeWords = ["bad", "poor", "terrible", "sad", "negative", "awful"];
let positiveCount = 0;
let negativeCount = 0;
words.forEach(word => {
if (positiveWords.includes(word.toLowerCase())) positiveCount++;
if (negativeWords.includes(word.toLowerCase())) negativeCount++;
});
let sentiment: string;
if (positiveCount > negativeCount) sentiment = "positive";
else if (negativeCount > positiveCount) sentiment = "negative";
else sentiment = "neutral";
// Extract key phrases (simple approach - just common bigrams)
const keyPhrases: string[] = [];
for (let i = 0; i < words.length - 1; i++) {
if (words[i].length > 3 && words[i + 1].length > 3) {
keyPhrases.push(`${words[i]} ${words[i + 1]}`);
}
}
return {
structuredContent: {
word_count: words.length,
sentence_count: sentences.length,
character_count: text.length,
reading_time_minutes: Math.round((words.length / wordsPerMinute) * 10) / 10,
sentiment,
key_phrases: keyPhrases.slice(0, 5) // Top 5 phrases
}
};
}
case "traditional_tool": {
const { message } = request.params.arguments as { message: string };
// Traditional tool returns content array
return {
content: [
{
type: "text",
text: `Processed message: ${message.toUpperCase()}`
}
]
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Output Schema Example Server running on stdio");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});

View file

@ -0,0 +1,93 @@
#!/usr/bin/env node
/**
* Client to test outputSchema servers
* This client connects to either the high-level or low-level outputSchema server
* and calls the get_weather tool to demonstrate structured output
*/
import { Client } from "../../client/index.js";
import { StdioClientTransport } from "../../client/stdio.js";
import { ListToolsResultSchema, CallToolResultSchema } from "../../types.js";
async function main() {
const serverPath = process.argv[2];
if (!serverPath) {
console.error("Usage: npx tsx testOutputSchemaServers.ts <server-path>");
console.error("Example: npx tsx testOutputSchemaServers.ts ./mcpServerOutputSchema.ts");
process.exit(1);
}
console.log(`Connecting to ${serverPath}...`);
// Create transport that spawns the server process
const transport = new StdioClientTransport({
command: "npx",
args: ["tsx", serverPath]
});
const client = new Client({
name: "output-schema-test-client",
version: "1.0.0"
}, {
capabilities: {}
});
try {
await client.connect(transport);
console.log("Connected to server\n");
// List available tools
console.log("Listing available tools...");
const toolsResult = await client.request({
method: "tools/list"
}, ListToolsResultSchema);
console.log("Available tools:");
for (const tool of toolsResult.tools) {
console.log(`- ${tool.name}: ${tool.description}`);
if (tool.outputSchema) {
console.log(" Has outputSchema: Yes");
console.log(" Output schema:", JSON.stringify(tool.outputSchema, null, 2));
} else {
console.log(" Has outputSchema: No");
}
}
// Call the weather tool
console.log("\nCalling get_weather tool...");
const weatherResult = await client.request({
method: "tools/call",
params: {
name: "get_weather",
arguments: {
city: "London",
country: "UK"
}
}
}, CallToolResultSchema);
console.log("\nWeather tool result:");
if (weatherResult.structuredContent) {
console.log("Structured content:");
console.log(JSON.stringify(weatherResult.structuredContent, null, 2));
}
if (weatherResult.content) {
console.log("Unstructured content:");
weatherResult.content.forEach(content => {
if (content.type === "text") {
console.log(content.text);
}
});
}
await client.close();
console.log("\nDisconnected from server");
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();