mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-19 08:33:23 +02:00
Merge pull request #1197 from modelcontextprotocol/ashwin/resources
Add embedded resource reference example to everything server
This commit is contained in:
20
src/everything/CLAUDE.md
Normal file
20
src/everything/CLAUDE.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# MCP "Everything" Server - Development Guidelines
|
||||||
|
|
||||||
|
## Build, Test & Run Commands
|
||||||
|
- Build: `npm run build` - Compiles TypeScript to JavaScript
|
||||||
|
- Watch mode: `npm run watch` - Watches for changes and rebuilds automatically
|
||||||
|
- Run server: `npm run start` - Starts the MCP server using stdio transport
|
||||||
|
- Run SSE server: `npm run start:sse` - Starts the MCP server with SSE transport
|
||||||
|
- Prepare release: `npm run prepare` - Builds the project for publishing
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
- Use ES modules with `.js` extension in import paths
|
||||||
|
- Strictly type all functions and variables with TypeScript
|
||||||
|
- Follow zod schema patterns for tool input validation
|
||||||
|
- Prefer async/await over callbacks and Promise chains
|
||||||
|
- Place all imports at top of file, grouped by external then internal
|
||||||
|
- Use descriptive variable names that clearly indicate purpose
|
||||||
|
- Implement proper cleanup for timers and resources in server shutdown
|
||||||
|
- Follow camelCase for variables/functions, PascalCase for types/classes, UPPER_CASE for constants
|
||||||
|
- Handle errors with try/catch blocks and provide clear error messages
|
||||||
|
- Use consistent indentation (2 spaces) and trailing commas in multi-line objects
|
||||||
@@ -63,6 +63,15 @@ This MCP server attempts to exercise all the features of the MCP protocol. It is
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
8. `getResourceReference`
|
||||||
|
- Returns a resource reference that can be used by MCP clients
|
||||||
|
- Inputs:
|
||||||
|
- `resourceId` (number, 1-100): ID of the resource to reference
|
||||||
|
- Returns: A resource reference with:
|
||||||
|
- Text introduction
|
||||||
|
- Embedded resource with `type: "resource"`
|
||||||
|
- Text instruction for using the resource URI
|
||||||
|
|
||||||
### Resources
|
### Resources
|
||||||
|
|
||||||
The server provides 100 test resources in two formats:
|
The server provides 100 test resources in two formats:
|
||||||
@@ -96,6 +105,13 @@ Resource features:
|
|||||||
- `style` (string): Output style preference
|
- `style` (string): Output style preference
|
||||||
- Returns: Multi-turn conversation with images
|
- Returns: Multi-turn conversation with images
|
||||||
|
|
||||||
|
3. `resource_prompt`
|
||||||
|
- Demonstrates embedding resource references in prompts
|
||||||
|
- Required arguments:
|
||||||
|
- `resourceId` (number): ID of the resource to embed (1-100)
|
||||||
|
- Returns: Multi-turn conversation with an embedded resource reference
|
||||||
|
- Shows how to include resources directly in prompt messages
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
|
|
||||||
The server sends random-leveled log messages every 15 seconds, e.g.:
|
The server sends random-leveled log messages every 15 seconds, e.g.:
|
||||||
|
|||||||
@@ -62,10 +62,21 @@ const EXAMPLE_COMPLETIONS = {
|
|||||||
const GetTinyImageSchema = z.object({});
|
const GetTinyImageSchema = z.object({});
|
||||||
|
|
||||||
const AnnotatedMessageSchema = z.object({
|
const AnnotatedMessageSchema = z.object({
|
||||||
messageType: z.enum(["error", "success", "debug"])
|
messageType: z
|
||||||
|
.enum(["error", "success", "debug"])
|
||||||
.describe("Type of message to demonstrate different annotation patterns"),
|
.describe("Type of message to demonstrate different annotation patterns"),
|
||||||
includeImage: z.boolean().default(false)
|
includeImage: z
|
||||||
.describe("Whether to include an example image")
|
.boolean()
|
||||||
|
.default(false)
|
||||||
|
.describe("Whether to include an example image"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const GetResourceReferenceSchema = z.object({
|
||||||
|
resourceId: z
|
||||||
|
.number()
|
||||||
|
.min(1)
|
||||||
|
.max(100)
|
||||||
|
.describe("ID of the resource to reference (1-100)"),
|
||||||
});
|
});
|
||||||
|
|
||||||
enum ToolName {
|
enum ToolName {
|
||||||
@@ -76,11 +87,13 @@ enum ToolName {
|
|||||||
SAMPLE_LLM = "sampleLLM",
|
SAMPLE_LLM = "sampleLLM",
|
||||||
GET_TINY_IMAGE = "getTinyImage",
|
GET_TINY_IMAGE = "getTinyImage",
|
||||||
ANNOTATED_MESSAGE = "annotatedMessage",
|
ANNOTATED_MESSAGE = "annotatedMessage",
|
||||||
|
GET_RESOURCE_REFERENCE = "getResourceReference",
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PromptName {
|
enum PromptName {
|
||||||
SIMPLE = "simple_prompt",
|
SIMPLE = "simple_prompt",
|
||||||
COMPLEX = "complex_prompt",
|
COMPLEX = "complex_prompt",
|
||||||
|
RESOURCE = "resource_prompt",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createServer = () => {
|
export const createServer = () => {
|
||||||
@@ -96,7 +109,7 @@ export const createServer = () => {
|
|||||||
tools: {},
|
tools: {},
|
||||||
logging: {},
|
logging: {},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let subscriptions: Set<string> = new Set();
|
let subscriptions: Set<string> = new Set();
|
||||||
@@ -115,36 +128,37 @@ export const createServer = () => {
|
|||||||
let logLevel: LoggingLevel = "debug";
|
let logLevel: LoggingLevel = "debug";
|
||||||
let logsUpdateInterval: NodeJS.Timeout | undefined;
|
let logsUpdateInterval: NodeJS.Timeout | undefined;
|
||||||
const messages = [
|
const messages = [
|
||||||
{level: "debug", data: "Debug-level message"},
|
{ level: "debug", data: "Debug-level message" },
|
||||||
{level: "info", data: "Info-level message"},
|
{ level: "info", data: "Info-level message" },
|
||||||
{level: "notice", data: "Notice-level message"},
|
{ level: "notice", data: "Notice-level message" },
|
||||||
{level: "warning", data: "Warning-level message"},
|
{ level: "warning", data: "Warning-level message" },
|
||||||
{level: "error", data: "Error-level message"},
|
{ level: "error", data: "Error-level message" },
|
||||||
{level: "critical", data: "Critical-level message"},
|
{ level: "critical", data: "Critical-level message" },
|
||||||
{level: "alert", data: "Alert level-message"},
|
{ level: "alert", data: "Alert level-message" },
|
||||||
{level: "emergency", data: "Emergency-level message"}
|
{ level: "emergency", data: "Emergency-level message" },
|
||||||
]
|
];
|
||||||
|
|
||||||
const isMessageIgnored = (level:LoggingLevel):boolean => {
|
const isMessageIgnored = (level: LoggingLevel): boolean => {
|
||||||
const currentLevel = messages.findIndex((msg) => logLevel === msg.level);
|
const currentLevel = messages.findIndex((msg) => logLevel === msg.level);
|
||||||
const messageLevel = messages.findIndex((msg) => level === msg.level);
|
const messageLevel = messages.findIndex((msg) => level === msg.level);
|
||||||
return messageLevel < currentLevel;
|
return messageLevel < currentLevel;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Set up update interval for random log messages
|
// Set up update interval for random log messages
|
||||||
logsUpdateInterval = setInterval(() => {
|
logsUpdateInterval = setInterval(() => {
|
||||||
let message = {
|
let message = {
|
||||||
method: "notifications/message",
|
method: "notifications/message",
|
||||||
params: messages[Math.floor(Math.random() * messages.length)],
|
params: messages[Math.floor(Math.random() * messages.length)],
|
||||||
}
|
};
|
||||||
if (!isMessageIgnored(message.params.level as LoggingLevel)) server.notification(message);
|
if (!isMessageIgnored(message.params.level as LoggingLevel))
|
||||||
|
server.notification(message);
|
||||||
}, 15000);
|
}, 15000);
|
||||||
|
|
||||||
// Helper method to request sampling from client
|
// Helper method to request sampling from client
|
||||||
const requestSampling = async (
|
const requestSampling = async (
|
||||||
context: string,
|
context: string,
|
||||||
uri: string,
|
uri: string,
|
||||||
maxTokens: number = 100,
|
maxTokens: number = 100
|
||||||
) => {
|
) => {
|
||||||
const request: CreateMessageRequest = {
|
const request: CreateMessageRequest = {
|
||||||
method: "sampling/createMessage",
|
method: "sampling/createMessage",
|
||||||
@@ -280,6 +294,17 @@ export const createServer = () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: PromptName.RESOURCE,
|
||||||
|
description: "A prompt that includes an embedded resource reference",
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: "resourceId",
|
||||||
|
description: "Resource ID to include (1-100)",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -330,6 +355,37 @@ export const createServer = () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === PromptName.RESOURCE) {
|
||||||
|
const resourceId = parseInt(args?.resourceId as string, 10);
|
||||||
|
if (isNaN(resourceId) || resourceId < 1 || resourceId > 100) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid resourceId: ${args?.resourceId}. Must be a number between 1 and 100.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceIndex = resourceId - 1;
|
||||||
|
const resource = ALL_RESOURCES[resourceIndex];
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: {
|
||||||
|
type: "text",
|
||||||
|
text: `This prompt includes Resource ${resourceId}. Please analyze the following resource:`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: {
|
||||||
|
type: "resource",
|
||||||
|
resource: resource,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown prompt: ${name}`);
|
throw new Error(`Unknown prompt: ${name}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -347,7 +403,8 @@ export const createServer = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ToolName.PRINT_ENV,
|
name: ToolName.PRINT_ENV,
|
||||||
description: "Prints all environment variables, helpful for debugging MCP server configuration",
|
description:
|
||||||
|
"Prints all environment variables, helpful for debugging MCP server configuration",
|
||||||
inputSchema: zodToJsonSchema(PrintEnvSchema) as ToolInput,
|
inputSchema: zodToJsonSchema(PrintEnvSchema) as ToolInput,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -368,9 +425,16 @@ export const createServer = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ToolName.ANNOTATED_MESSAGE,
|
name: ToolName.ANNOTATED_MESSAGE,
|
||||||
description: "Demonstrates how annotations can be used to provide metadata about content",
|
description:
|
||||||
|
"Demonstrates how annotations can be used to provide metadata about content",
|
||||||
inputSchema: zodToJsonSchema(AnnotatedMessageSchema) as ToolInput,
|
inputSchema: zodToJsonSchema(AnnotatedMessageSchema) as ToolInput,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: ToolName.GET_RESOURCE_REFERENCE,
|
||||||
|
description:
|
||||||
|
"Returns a resource reference that can be used by MCP clients",
|
||||||
|
inputSchema: zodToJsonSchema(GetResourceReferenceSchema) as ToolInput,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return { tools };
|
return { tools };
|
||||||
@@ -407,7 +471,7 @@ export const createServer = () => {
|
|||||||
|
|
||||||
for (let i = 1; i < steps + 1; i++) {
|
for (let i = 1; i < steps + 1; i++) {
|
||||||
await new Promise((resolve) =>
|
await new Promise((resolve) =>
|
||||||
setTimeout(resolve, stepDuration * 1000),
|
setTimeout(resolve, stepDuration * 1000)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (progressToken !== undefined) {
|
if (progressToken !== undefined) {
|
||||||
@@ -450,10 +514,12 @@ export const createServer = () => {
|
|||||||
const result = await requestSampling(
|
const result = await requestSampling(
|
||||||
prompt,
|
prompt,
|
||||||
ToolName.SAMPLE_LLM,
|
ToolName.SAMPLE_LLM,
|
||||||
maxTokens,
|
maxTokens
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: `LLM sampling result: ${result.content.text}` }],
|
content: [
|
||||||
|
{ type: "text", text: `LLM sampling result: ${result.content.text}` },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,6 +544,35 @@ export const createServer = () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === ToolName.GET_RESOURCE_REFERENCE) {
|
||||||
|
const validatedArgs = GetResourceReferenceSchema.parse(args);
|
||||||
|
const resourceId = validatedArgs.resourceId;
|
||||||
|
|
||||||
|
const resourceIndex = resourceId - 1;
|
||||||
|
if (resourceIndex < 0 || resourceIndex >= ALL_RESOURCES.length) {
|
||||||
|
throw new Error(`Resource with ID ${resourceId} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = ALL_RESOURCES[resourceIndex];
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Returning resource reference for Resource ${resourceId}:`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "resource",
|
||||||
|
resource: resource,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `You can access this resource using the URI: ${resource.uri}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (name === ToolName.ANNOTATED_MESSAGE) {
|
if (name === ToolName.ANNOTATED_MESSAGE) {
|
||||||
const { messageType, includeImage } = AnnotatedMessageSchema.parse(args);
|
const { messageType, includeImage } = AnnotatedMessageSchema.parse(args);
|
||||||
|
|
||||||
@@ -490,8 +585,8 @@ export const createServer = () => {
|
|||||||
text: "Error: Operation failed",
|
text: "Error: Operation failed",
|
||||||
annotations: {
|
annotations: {
|
||||||
priority: 1.0, // Errors are highest priority
|
priority: 1.0, // Errors are highest priority
|
||||||
audience: ["user", "assistant"] // Both need to know about errors
|
audience: ["user", "assistant"], // Both need to know about errors
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} else if (messageType === "success") {
|
} else if (messageType === "success") {
|
||||||
content.push({
|
content.push({
|
||||||
@@ -499,8 +594,8 @@ export const createServer = () => {
|
|||||||
text: "Operation completed successfully",
|
text: "Operation completed successfully",
|
||||||
annotations: {
|
annotations: {
|
||||||
priority: 0.7, // Success messages are important but not critical
|
priority: 0.7, // Success messages are important but not critical
|
||||||
audience: ["user"] // Success mainly for user consumption
|
audience: ["user"], // Success mainly for user consumption
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} else if (messageType === "debug") {
|
} else if (messageType === "debug") {
|
||||||
content.push({
|
content.push({
|
||||||
@@ -508,8 +603,8 @@ export const createServer = () => {
|
|||||||
text: "Debug: Cache hit ratio 0.95, latency 150ms",
|
text: "Debug: Cache hit ratio 0.95, latency 150ms",
|
||||||
annotations: {
|
annotations: {
|
||||||
priority: 0.3, // Debug info is low priority
|
priority: 0.3, // Debug info is low priority
|
||||||
audience: ["assistant"] // Technical details for assistant
|
audience: ["assistant"], // Technical details for assistant
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,8 +616,8 @@ export const createServer = () => {
|
|||||||
mimeType: "image/png",
|
mimeType: "image/png",
|
||||||
annotations: {
|
annotations: {
|
||||||
priority: 0.5,
|
priority: 0.5,
|
||||||
audience: ["user"] // Images primarily for user visualization
|
audience: ["user"], // Images primarily for user visualization
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,7 +635,7 @@ export const createServer = () => {
|
|||||||
if (!resourceId) return { completion: { values: [] } };
|
if (!resourceId) return { completion: { values: [] } };
|
||||||
|
|
||||||
// Filter resource IDs that start with the input value
|
// Filter resource IDs that start with the input value
|
||||||
const values = EXAMPLE_COMPLETIONS.resourceId.filter(id =>
|
const values = EXAMPLE_COMPLETIONS.resourceId.filter((id) =>
|
||||||
id.startsWith(argument.value)
|
id.startsWith(argument.value)
|
||||||
);
|
);
|
||||||
return { completion: { values, hasMore: false, total: values.length } };
|
return { completion: { values, hasMore: false, total: values.length } };
|
||||||
@@ -548,10 +643,11 @@ export const createServer = () => {
|
|||||||
|
|
||||||
if (ref.type === "ref/prompt") {
|
if (ref.type === "ref/prompt") {
|
||||||
// Handle completion for prompt arguments
|
// Handle completion for prompt arguments
|
||||||
const completions = EXAMPLE_COMPLETIONS[argument.name as keyof typeof EXAMPLE_COMPLETIONS];
|
const completions =
|
||||||
|
EXAMPLE_COMPLETIONS[argument.name as keyof typeof EXAMPLE_COMPLETIONS];
|
||||||
if (!completions) return { completion: { values: [] } };
|
if (!completions) return { completion: { values: [] } };
|
||||||
|
|
||||||
const values = completions.filter(value =>
|
const values = completions.filter((value) =>
|
||||||
value.startsWith(argument.value)
|
value.startsWith(argument.value)
|
||||||
);
|
);
|
||||||
return { completion: { values, hasMore: false, total: values.length } };
|
return { completion: { values, hasMore: false, total: values.length } };
|
||||||
|
|||||||
Reference in New Issue
Block a user