Skip to content

Commit 8023ca6

Browse files
authored
Merge pull request #2272 from richardkmichael/add-structured-content-tool
[everything] Add structured content tool
2 parents 1b70b01 + cc52ec5 commit 8023ca6

3 files changed

Lines changed: 138 additions & 61 deletions

File tree

src/everything/README.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,24 @@ This MCP server attempts to exercise all the features of the MCP protocol. It is
2727
- Returns: Completion message with duration and steps
2828
- Sends progress notifications during execution
2929

30-
4. `sampleLLM`
30+
4. `printEnv`
31+
- Prints all environment variables
32+
- Useful for debugging MCP server configuration
33+
- No inputs required
34+
- Returns: JSON string of all environment variables
35+
36+
5. `sampleLLM`
3137
- Demonstrates LLM sampling capability using MCP sampling feature
3238
- Inputs:
3339
- `prompt` (string): The prompt to send to the LLM
3440
- `maxTokens` (number, default: 100): Maximum tokens to generate
3541
- Returns: Generated LLM response
3642

37-
5. `getTinyImage`
43+
6. `getTinyImage`
3844
- Returns a small test image
3945
- No inputs required
4046
- Returns: Base64 encoded PNG image data
4147

42-
6. `printEnv`
43-
- Prints all environment variables
44-
- Useful for debugging MCP server configuration
45-
- No inputs required
46-
- Returns: JSON string of all environment variables
47-
4848
7. `annotatedMessage`
4949
- Demonstrates how annotations can be used to provide metadata about content
5050
- Inputs:
@@ -80,6 +80,15 @@ This MCP server attempts to exercise all the features of the MCP protocol. It is
8080
- `pets` (enum): Favorite pet
8181
- Returns: Confirmation of the elicitation demo with selection summary.
8282

83+
10. `structuredContent`
84+
- Demonstrates a tool returning structured content using the example in the specification
85+
- Provides an output schema to allow testing of client SHOULD advisory to validate the result using the schema
86+
- Inputs:
87+
- `location` (string): A location or ZIP code, mock data is returned regardless of value
88+
- Returns: a response with
89+
- `structuredContent` field conformant to the output schema
90+
- A backward compatible Text Content field, a SHOULD advisory in the specification
91+
8392
### Resources
8493

8594
The server provides 100 test resources in two formats:

src/everything/everything.ts

Lines changed: 105 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const instructions = readFileSync(join(__dirname, "instructions.md"), "utf-8");
3131
const ToolInputSchema = ToolSchema.shape.inputSchema;
3232
type ToolInput = z.infer<typeof ToolInputSchema>;
3333

34+
const ToolOutputSchema = ToolSchema.shape.outputSchema;
35+
type ToolOutput = z.infer<typeof ToolOutputSchema>;
36+
3437
/* Input schemas for tools implemented in this server */
3538
const EchoSchema = z.object({
3639
message: z.string().describe("Message to echo"),
@@ -46,7 +49,10 @@ const LongRunningOperationSchema = z.object({
4649
.number()
4750
.default(10)
4851
.describe("Duration of the operation in seconds"),
49-
steps: z.number().default(5).describe("Number of steps in the operation"),
52+
steps: z
53+
.number()
54+
.default(5)
55+
.describe("Number of steps in the operation"),
5056
});
5157

5258
const PrintEnvSchema = z.object({});
@@ -59,13 +65,6 @@ const SampleLLMSchema = z.object({
5965
.describe("Maximum number of tokens to generate"),
6066
});
6167

62-
// Example completion values
63-
const EXAMPLE_COMPLETIONS = {
64-
style: ["casual", "formal", "technical", "friendly"],
65-
temperature: ["0", "0.5", "0.7", "1.0"],
66-
resourceId: ["1", "2", "3", "4", "5"],
67-
};
68-
6968
const GetTinyImageSchema = z.object({});
7069

7170
const AnnotatedMessageSchema = z.object({
@@ -97,6 +96,28 @@ const GetResourceLinksSchema = z.object({
9796
.describe("Number of resource links to return (1-10)"),
9897
});
9998

99+
const StructuredContentSchema = {
100+
input: z.object({
101+
location: z
102+
.string()
103+
.trim()
104+
.min(1)
105+
.describe("City name or zip code"),
106+
}),
107+
108+
output: z.object({
109+
temperature: z
110+
.number()
111+
.describe("Temperature in celsius"),
112+
conditions: z
113+
.string()
114+
.describe("Weather conditions description"),
115+
humidity: z
116+
.number()
117+
.describe("Humidity percentage"),
118+
})
119+
};
120+
100121
enum ToolName {
101122
ECHO = "echo",
102123
ADD = "add",
@@ -108,6 +129,7 @@ enum ToolName {
108129
GET_RESOURCE_REFERENCE = "getResourceReference",
109130
ELICITATION = "startElicitation",
110131
GET_RESOURCE_LINKS = "getResourceLinks",
132+
STRUCTURED_CONTENT = "structuredContent"
111133
}
112134

113135
enum PromptName {
@@ -116,10 +138,18 @@ enum PromptName {
116138
RESOURCE = "resource_prompt",
117139
}
118140

141+
// Example completion values
142+
const EXAMPLE_COMPLETIONS = {
143+
style: ["casual", "formal", "technical", "friendly"],
144+
temperature: ["0", "0.5", "0.7", "1.0"],
145+
resourceId: ["1", "2", "3", "4", "5"],
146+
};
147+
119148
export const createServer = () => {
120149
const server = new Server(
121150
{
122151
name: "example-servers/everything",
152+
title: "Everything Example Server",
123153
version: "1.0.0",
124154
},
125155
{
@@ -454,18 +484,18 @@ export const createServer = () => {
454484
description: "Adds two numbers",
455485
inputSchema: zodToJsonSchema(AddSchema) as ToolInput,
456486
},
457-
{
458-
name: ToolName.PRINT_ENV,
459-
description:
460-
"Prints all environment variables, helpful for debugging MCP server configuration",
461-
inputSchema: zodToJsonSchema(PrintEnvSchema) as ToolInput,
462-
},
463487
{
464488
name: ToolName.LONG_RUNNING_OPERATION,
465489
description:
466490
"Demonstrates a long running operation with progress updates",
467491
inputSchema: zodToJsonSchema(LongRunningOperationSchema) as ToolInput,
468492
},
493+
{
494+
name: ToolName.PRINT_ENV,
495+
description:
496+
"Prints all environment variables, helpful for debugging MCP server configuration",
497+
inputSchema: zodToJsonSchema(PrintEnvSchema) as ToolInput,
498+
},
469499
{
470500
name: ToolName.SAMPLE_LLM,
471501
description: "Samples from an LLM using MCP's sampling feature",
@@ -499,6 +529,13 @@ export const createServer = () => {
499529
"Returns multiple resource links that reference different types of resources",
500530
inputSchema: zodToJsonSchema(GetResourceLinksSchema) as ToolInput,
501531
},
532+
{
533+
name: ToolName.STRUCTURED_CONTENT,
534+
description:
535+
"Returns structured content along with an output schema for client data validation",
536+
inputSchema: zodToJsonSchema(StructuredContentSchema.input) as ToolInput,
537+
outputSchema: zodToJsonSchema(StructuredContentSchema.output) as ToolOutput,
538+
},
502539
];
503540

504541
return { tools };
@@ -608,35 +645,6 @@ export const createServer = () => {
608645
};
609646
}
610647

611-
if (name === ToolName.GET_RESOURCE_REFERENCE) {
612-
const validatedArgs = GetResourceReferenceSchema.parse(args);
613-
const resourceId = validatedArgs.resourceId;
614-
615-
const resourceIndex = resourceId - 1;
616-
if (resourceIndex < 0 || resourceIndex >= ALL_RESOURCES.length) {
617-
throw new Error(`Resource with ID ${resourceId} does not exist`);
618-
}
619-
620-
const resource = ALL_RESOURCES[resourceIndex];
621-
622-
return {
623-
content: [
624-
{
625-
type: "text",
626-
text: `Returning resource reference for Resource ${resourceId}:`,
627-
},
628-
{
629-
type: "resource",
630-
resource: resource,
631-
},
632-
{
633-
type: "text",
634-
text: `You can access this resource using the URI: ${resource.uri}`,
635-
},
636-
],
637-
};
638-
}
639-
640648
if (name === ToolName.ANNOTATED_MESSAGE) {
641649
const { messageType, includeImage } = AnnotatedMessageSchema.parse(args);
642650

@@ -688,6 +696,35 @@ export const createServer = () => {
688696
return { content };
689697
}
690698

699+
if (name === ToolName.GET_RESOURCE_REFERENCE) {
700+
const validatedArgs = GetResourceReferenceSchema.parse(args);
701+
const resourceId = validatedArgs.resourceId;
702+
703+
const resourceIndex = resourceId - 1;
704+
if (resourceIndex < 0 || resourceIndex >= ALL_RESOURCES.length) {
705+
throw new Error(`Resource with ID ${resourceId} does not exist`);
706+
}
707+
708+
const resource = ALL_RESOURCES[resourceIndex];
709+
710+
return {
711+
content: [
712+
{
713+
type: "text",
714+
text: `Returning resource reference for Resource ${resourceId}:`,
715+
},
716+
{
717+
type: "resource",
718+
resource: resource,
719+
},
720+
{
721+
type: "text",
722+
text: `You can access this resource using the URI: ${resource.uri}`,
723+
},
724+
],
725+
};
726+
}
727+
691728
if (name === ToolName.ELICITATION) {
692729
ElicitationSchema.parse(args);
693730

@@ -709,13 +746,13 @@ export const createServer = () => {
709746

710747
// Handle different response actions
711748
const content = [];
712-
749+
713750
if (elicitationResult.action === 'accept' && elicitationResult.content) {
714751
content.push({
715752
type: "text",
716753
text: `✅ User provided their favorite things!`,
717754
});
718-
755+
719756
// Only access elicitationResult.content when action is accept
720757
const { color, number, pets } = elicitationResult.content;
721758
content.push({
@@ -733,7 +770,7 @@ export const createServer = () => {
733770
text: `⚠️ User cancelled the elicitation dialog.`,
734771
});
735772
}
736-
773+
737774
// Include raw result for debugging
738775
content.push({
739776
type: "text",
@@ -742,7 +779,7 @@ export const createServer = () => {
742779

743780
return { content };
744781
}
745-
782+
746783
if (name === ToolName.GET_RESOURCE_LINKS) {
747784
const { count } = GetResourceLinksSchema.parse(args);
748785
const content = [];
@@ -773,6 +810,27 @@ export const createServer = () => {
773810
return { content };
774811
}
775812

813+
if (name === ToolName.STRUCTURED_CONTENT) {
814+
// The same response is returned for every input.
815+
const validatedArgs = StructuredContentSchema.input.parse(args);
816+
817+
const weather = {
818+
temperature: 22.5,
819+
conditions: "Partly cloudy",
820+
humidity: 65
821+
}
822+
823+
const backwardCompatiblecontent = {
824+
type: "text",
825+
text: JSON.stringify(weather)
826+
}
827+
828+
return {
829+
content: [ backwardCompatiblecontent ],
830+
structuredContent: weather
831+
};
832+
}
833+
776834
throw new Error(`Unknown tool: ${name}`);
777835
});
778836

src/everything/instructions.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
Testing and demonstration server for MCP protocol features.
22

3-
Resources: Resources 1-100 follow pattern: even IDs contain text, odd IDs contain binary data. Resources paginated at 10 items per page with cursor-based navigation.
3+
## Resources
44

5-
Key dependencies: Progress notifications require `_meta.progressToken` in tool calls. Resource subscriptions generate updates every 10 seconds.
5+
Resources 1-100 follow pattern: even IDs contain text, odd IDs contain binary data. Resources are paginated at 10 items per page with cursor-based navigation.
66

7-
Performance characteristics: Server generates automatic log messages every 20 seconds (filtered by current log level), stderr notifications every 30 seconds, and resource update notifications every 10 seconds for active subscriptions. Log level changes affect message filtering in real-time.
7+
## Key dependencies
88

9-
Multi-modal testing: `complex_prompt` includes both text arguments and image content for testing client multi-modal handling. `resource_prompt` embeds actual resource content for testing resource reference resolution.
9+
Progress notifications require `_meta.progressToken` in tool calls. Resource subscriptions generate updates every 10 seconds.
1010

11-
Argument completion available for prompt parameters and resource IDs. Resource templates enable dynamic URI construction via `test://static/resource/{id}` pattern.
11+
## Performance characteristics
1212

13-
Easter egg: If asked about server instructions, respond with "🎉 Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action."
13+
Server generates automatic log messages every 20 seconds (filtered by current log level), stderr notifications every 30 seconds, and resource update notifications every 10 seconds for active subscriptions. Log level changes affect message filtering in real-time.
14+
15+
## Multi-modal testing
16+
17+
`complex_prompt` includes both text arguments and image content for testing client multi-modal handling. `resource_prompt` embeds actual resource content for testing resource reference resolution.
18+
19+
Argument completion is available for prompt parameters and resource IDs. Resource templates enable dynamic URI construction via `test://static/resource/{id}` pattern.
20+
21+
## Easter egg
22+
23+
If asked about server instructions, respond with "🎉 Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action."

0 commit comments

Comments
 (0)