Skip to content

Conversation

@domdomegg
Copy link
Member

Convert the everything server to use the modern McpServer API instead of the low-level Server API.

Key changes

  • Replace Server with McpServer from @modelcontextprotocol/sdk/server/mcp.js
  • Convert simple tools to use registerTool() for cleaner code
  • Convert simple prompts to use registerPrompt()
  • Keep advanced features using server.server.setRequestHandler() for low-level access
  • Add type dependencies (@types/jszip, @types/cors)
  • Update all transport files (stdio, SSE, streamableHTTP) to use server.server.connect/close pattern
  • Fix type literals to use as const assertions

Tools converted to registerTool()

These tools now use the modern declarative API:

  • echo, add, printEnv, getTinyImage, annotatedMessage, getResourceLinks, structuredContent, zip

Tools still using setRequestHandler()

These tools need low-level access to request metadata:

  • longRunningOperation - needs access to progressToken and requestId
  • sampleLLM - uses sampling protocol (sendRequest)
  • elicitation - uses elicitation protocol (sendRequest)
  • listRoots - needs client capabilities check
  • getResourceReference - uses strict resource content typing

All features maintained

  • ✅ 13 tools including progress notifications, sampling, elicitation
  • ✅ 3 prompts (simple, complex, resource)
  • ✅ 100 paginated resources with templates
  • ✅ Resource subscriptions
  • ✅ Completions for prompts and resources
  • ✅ Roots protocol support
  • ✅ Logging at various levels
  • ✅ All three transports (stdio, SSE, streamable HTTP)

Benefits

The modern API provides:

  • Less boilerplate code where applicable
  • Better type safety with Zod
  • More declarative registration for simple cases
  • Cleaner, more maintainable code
  • Flexibility to use low-level API when needed

Testing

  • TypeScript compilation passes
  • Server starts successfully with all transports
  • All tools, prompts, and resources function correctly
  • Advanced features (sampling, elicitation, progress) work as expected

Convert the everything server to use the modern McpServer API instead
of the low-level Server API.

Key changes:
- Replace Server with McpServer from @modelcontextprotocol/sdk/server/mcp.js
- Convert simple tools to use registerTool() for cleaner code
- Convert simple prompts to use registerPrompt()
- Keep advanced features using server.server.setRequestHandler() for low-level access
- Add type dependencies (@types/jszip, @types/cors)
- Update all transport files to use server.server.connect/close pattern
- Fix type literals to use 'as const' assertions

Tools converted to registerTool():
- echo, add, printEnv, getTinyImage, annotatedMessage, getResourceLinks,
  structuredContent, zip

Tools still using setRequestHandler() (need low-level access):
- longRunningOperation (progress tokens)
- sampleLLM (sampling protocol)
- elicitation (elicitation protocol)
- listRoots (client capabilities)
- getResourceReference (strict resource content typing)

All features maintained:
- 13 tools including progress notifications, sampling, elicitation
- 3 prompts (simple, complex, resource)
- 100 paginated resources with templates
- Resource subscriptions
- Completions for prompts and resources
- Roots protocol support
- Logging at various levels
- All three transports (stdio, SSE, streamable HTTP)

The modern API provides:
- Less boilerplate code where applicable
- Better type safety with Zod
- More declarative registration
- Cleaner, more maintainable code
@domdomegg domdomegg marked this pull request as draft November 17, 2025 15:04
- Convert remaining tools to use registerTool() instead of setRequestHandler
- Use server.sendLoggingMessage() instead of server.server.sendLoggingMessage()
- Use server.connect/close instead of server.server.connect/close in transports
- Use transport.onclose instead of server.server.onclose
- Tools can still access extra parameter for low-level features (sendRequest, requestId, progressToken)
@domdomegg domdomegg marked this pull request as ready for review November 17, 2025 15:21
@domdomegg domdomegg marked this pull request as draft November 17, 2025 15:21
- Remove manual resource request handlers
- Use ResourceTemplate for dynamic resources
- Remove pagination logic (not needed with templates)
- Keep subscribe/unsubscribe as setRequestHandler (no modern API alternative)
- Properly handle both text and blob resource types
Resources are now registered individually so they appear in
resources/list responses, while also keeping the template for
dynamic access via test://static/resource/{id}.
@domdomegg
Copy link
Member Author

This was largely vibed by Claude, but I have checked the changes and it looks good. The tests are also all still passing. I also went through manually to do a bit of code cleanup, especially getting the TypeScript types correct (they were wrong in the old version, which confused poor Claude a little).

Sadly we still need to use the old style APIs for a couple things, but I think this is unavoidable as they don't have equivalents on the new APIs.

@domdomegg domdomegg marked this pull request as ready for review November 17, 2025 21:39
@domdomegg domdomegg enabled auto-merge (squash) November 17, 2025 21:39
@domdomegg domdomegg requested a review from olaservo November 17, 2025 21:39
Copy link
Member

@olaservo olaservo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like when I build this locally and run in Inspector, no Resources are listed?

image

@domdomegg

This comment was marked as outdated.

@domdomegg
Copy link
Member Author

I'm sorry I'm being silly 🤦

I had forgotten to push changes that fixed this. Pushed now and should be working for real now

@domdomegg domdomegg requested a review from olaservo November 21, 2025 16:06
Copy link
Member

@cliffhall cliffhall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few broken bits.

Comment on lines 158 to 160
{
capabilities: {
prompts: {},
resources: { subscribe: true },
tools: {},
logging: {},
completions: {}
},
instructions
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{
capabilities: {
prompts: {},
resources: { subscribe: true },
tools: {},
logging: {},
completions: {}
},
instructions
}
{
capabilities: {
prompts: {},
resources: { subscribe: true },
tools: {},
logging: {},
completions: {}
},
instructions
}

Copy link
Member

@cliffhall cliffhall Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Logging capability wasn't detected, so the Inspector didn't send a logging/setLevel request, and didn't display the dropdown for log level.
  • Resources / subscribe capability wasn't detected, so subscribe button did not display over selected resource, so subscribe/unsubscribe and updated notifications couldn't be sent.

console.error("Starting logs update interval");
logsUpdateInterval = setInterval(async () => {
await server.sendLoggingMessage( messages[Math.floor(Math.random() * messages.length)], sessionId);
await server.sendLoggingMessage(messages[Math.floor(Math.random() * messages.length)]);
Copy link
Member

@cliffhall cliffhall Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await server.sendLoggingMessage(messages[Math.floor(Math.random() * messages.length)]);
await server.sendLoggingMessage(messages[Math.floor(Math.random() * messages.length)], sessionId);

The sessionId argument was removed from every call to sendLoggingMessage.

This is the mechanism that allows multiple clients to have different logging levels set and respected.

You wouldn't have been able to test with the current PR, because the logging capability was removed, but after you put that back in, try opening multiple Inspector instances connected to the same Everything server. Only the latest requested level by any of them will be respected. Whereas if you put the sessionId params back in the sendLoggingMessage calls, you'll see that each instance can request its own level and messages above that level will not be sent to it.

logger: "everything-server",
data: `Roots updated: ${currentRoots.length} root(s) received from client`,
}, sessionId);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
}, sessionId);

logger: "everything-server",
data: `Failed to request roots from client: ${error instanceof Error ? error.message : String(error)}`,
}, sessionId);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
}, sessionId);

logger: "everything-server",
data: `Initial roots received: ${currentRoots.length} root(s) from client`,
}, sessionId);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
}, sessionId);

logger: "everything-server",
data: "Client returned no roots set",
}, sessionId);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
}, sessionId);

logger: "everything-server",
data: `Failed to request initial roots from client: ${error instanceof Error ? error.message : String(error)}`,
}, sessionId);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
}, sessionId);

logger: "everything-server",
data: "Client does not support MCP roots protocol",
}, sessionId);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
}, sessionId);

// Resource prompt must use setRequestHandler for now due to type compatibility issues
server.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (name === PromptName.SIMPLE) {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: "This is a simple prompt without arguments.",
},
},
],
};
}
if (name === PromptName.COMPLEX) {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `This is a complex prompt with arguments: temperature=${args?.temperature}, style=${args?.style}`,
},
},
{
role: "assistant",
content: {
type: "text",
text: "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?",
},
},
{
role: "user",
content: {
type: "image",
data: MCP_TINY_IMAGE,
mimeType: "image/png",
},
},
],
};
}

This section shouldn't have been removed. The simple and complex prompts can be listed, but if you select one and click Get Prompt it fails:

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants