Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/everything/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions src/everything/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

The server provides 100 test resources in two formats:
Expand Down Expand Up @@ -96,6 +105,13 @@ Resource features:
- `style` (string): Output style preference
- 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

The server sends random-leveled log messages every 15 seconds, e.g.:
Expand Down
168 changes: 132 additions & 36 deletions src/everything/everything.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,21 @@ const EXAMPLE_COMPLETIONS = {
const GetTinyImageSchema = 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"),
includeImage: z.boolean().default(false)
.describe("Whether to include an example image")
includeImage: z
.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 {
Expand All @@ -76,11 +87,13 @@ enum ToolName {
SAMPLE_LLM = "sampleLLM",
GET_TINY_IMAGE = "getTinyImage",
ANNOTATED_MESSAGE = "annotatedMessage",
GET_RESOURCE_REFERENCE = "getResourceReference",
}

enum PromptName {
SIMPLE = "simple_prompt",
COMPLEX = "complex_prompt",
RESOURCE = "resource_prompt",
}

export const createServer = () => {
Expand All @@ -96,7 +109,7 @@ export const createServer = () => {
tools: {},
logging: {},
},
},
}
);

let subscriptions: Set<string> = new Set();
Expand All @@ -115,36 +128,37 @@ export const createServer = () => {
let logLevel: LoggingLevel = "debug";
let logsUpdateInterval: NodeJS.Timeout | undefined;
const messages = [
{level: "debug", data: "Debug-level message"},
{level: "info", data: "Info-level message"},
{level: "notice", data: "Notice-level message"},
{level: "warning", data: "Warning-level message"},
{level: "error", data: "Error-level message"},
{level: "critical", data: "Critical-level message"},
{level: "alert", data: "Alert level-message"},
{level: "emergency", data: "Emergency-level message"}
]

const isMessageIgnored = (level:LoggingLevel):boolean => {
{ level: "debug", data: "Debug-level message" },
{ level: "info", data: "Info-level message" },
{ level: "notice", data: "Notice-level message" },
{ level: "warning", data: "Warning-level message" },
{ level: "error", data: "Error-level message" },
{ level: "critical", data: "Critical-level message" },
{ level: "alert", data: "Alert level-message" },
{ level: "emergency", data: "Emergency-level message" },
];

const isMessageIgnored = (level: LoggingLevel): boolean => {
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;
}
};

// Set up update interval for random log messages
logsUpdateInterval = setInterval(() => {
let message = {
method: "notifications/message",
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);

// Helper method to request sampling from client
const requestSampling = async (
context: string,
uri: string,
maxTokens: number = 100,
maxTokens: number = 100
) => {
const request: CreateMessageRequest = {
method: "sampling/createMessage",
Expand Down Expand Up @@ -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,
},
],
},
],
};
});
Expand Down Expand Up @@ -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}`);
});

Expand All @@ -347,7 +403,8 @@ export const createServer = () => {
},
{
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,
},
{
Expand All @@ -368,9 +425,16 @@ export const createServer = () => {
},
{
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,
},
{
name: ToolName.GET_RESOURCE_REFERENCE,
description:
"Returns a resource reference that can be used by MCP clients",
inputSchema: zodToJsonSchema(GetResourceReferenceSchema) as ToolInput,
},
];

return { tools };
Expand Down Expand Up @@ -407,7 +471,7 @@ export const createServer = () => {

for (let i = 1; i < steps + 1; i++) {
await new Promise((resolve) =>
setTimeout(resolve, stepDuration * 1000),
setTimeout(resolve, stepDuration * 1000)
);

if (progressToken !== undefined) {
Expand Down Expand Up @@ -450,10 +514,12 @@ export const createServer = () => {
const result = await requestSampling(
prompt,
ToolName.SAMPLE_LLM,
maxTokens,
maxTokens
);
return {
content: [{ type: "text", text: `LLM sampling result: ${result.content.text}` }],
content: [
{ type: "text", text: `LLM sampling result: ${result.content.text}` },
],
};
}

Expand All @@ -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) {
const { messageType, includeImage } = AnnotatedMessageSchema.parse(args);

Expand All @@ -490,26 +585,26 @@ export const createServer = () => {
text: "Error: Operation failed",
annotations: {
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") {
content.push({
type: "text",
text: "Operation completed successfully",
annotations: {
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") {
content.push({
type: "text",
text: "Debug: Cache hit ratio 0.95, latency 150ms",
annotations: {
priority: 0.3, // Debug info is low priority
audience: ["assistant"] // Technical details for assistant
}
audience: ["assistant"], // Technical details for assistant
},
});
}

Expand All @@ -521,8 +616,8 @@ export const createServer = () => {
mimeType: "image/png",
annotations: {
priority: 0.5,
audience: ["user"] // Images primarily for user visualization
}
audience: ["user"], // Images primarily for user visualization
},
});
}

Expand All @@ -540,18 +635,19 @@ export const createServer = () => {
if (!resourceId) return { completion: { values: [] } };

// 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)
);
return { completion: { values, hasMore: false, total: values.length } };
}

if (ref.type === "ref/prompt") {
// 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: [] } };

const values = completions.filter(value =>
const values = completions.filter((value) =>
value.startsWith(argument.value)
);
return { completion: { values, hasMore: false, total: values.length } };
Expand Down