修改后台权限

This commit is contained in:
yoyuzh
2026-03-24 14:30:59 +08:00
parent 00f902f475
commit b2d9db7be9
9310 changed files with 1246063 additions and 48 deletions

View File

@@ -0,0 +1,78 @@
import { AuthorizationParams, OAuthServerProvider } from '../../server/auth/provider.js';
import { OAuthRegisteredClientsStore } from '../../server/auth/clients.js';
import { OAuthClientInformationFull, OAuthMetadata, OAuthTokens } from '../../shared/auth.js';
import { Response } from 'express';
import { AuthInfo } from '../../server/auth/types.js';
export declare class DemoInMemoryClientsStore implements OAuthRegisteredClientsStore {
private clients;
getClient(clientId: string): Promise<{
redirect_uris: string[];
client_id: string;
token_endpoint_auth_method?: string | undefined;
grant_types?: string[] | undefined;
response_types?: string[] | undefined;
client_name?: string | undefined;
client_uri?: string | undefined;
logo_uri?: string | undefined;
scope?: string | undefined;
contacts?: string[] | undefined;
tos_uri?: string | undefined;
policy_uri?: string | undefined;
jwks_uri?: string | undefined;
jwks?: any;
software_id?: string | undefined;
software_version?: string | undefined;
software_statement?: string | undefined;
client_secret?: string | undefined;
client_id_issued_at?: number | undefined;
client_secret_expires_at?: number | undefined;
} | undefined>;
registerClient(clientMetadata: OAuthClientInformationFull): Promise<{
redirect_uris: string[];
client_id: string;
token_endpoint_auth_method?: string | undefined;
grant_types?: string[] | undefined;
response_types?: string[] | undefined;
client_name?: string | undefined;
client_uri?: string | undefined;
logo_uri?: string | undefined;
scope?: string | undefined;
contacts?: string[] | undefined;
tos_uri?: string | undefined;
policy_uri?: string | undefined;
jwks_uri?: string | undefined;
jwks?: any;
software_id?: string | undefined;
software_version?: string | undefined;
software_statement?: string | undefined;
client_secret?: string | undefined;
client_id_issued_at?: number | undefined;
client_secret_expires_at?: number | undefined;
}>;
}
/**
* 🚨 DEMO ONLY - NOT FOR PRODUCTION
*
* This example demonstrates MCP OAuth flow but lacks some of the features required for production use,
* for example:
* - Persistent token storage
* - Rate limiting
*/
export declare class DemoInMemoryAuthProvider implements OAuthServerProvider {
private validateResource?;
clientsStore: DemoInMemoryClientsStore;
private codes;
private tokens;
constructor(validateResource?: ((resource?: URL) => boolean) | undefined);
authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
challengeForAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise<string>;
exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, _codeVerifier?: string): Promise<OAuthTokens>;
exchangeRefreshToken(_client: OAuthClientInformationFull, _refreshToken: string, _scopes?: string[], _resource?: URL): Promise<OAuthTokens>;
verifyAccessToken(token: string): Promise<AuthInfo>;
}
export declare const setupAuthServer: ({ authServerUrl, mcpServerUrl, strictResource }: {
authServerUrl: URL;
mcpServerUrl: URL;
strictResource: boolean;
}) => OAuthMetadata;
//# sourceMappingURL=demoInMemoryOAuthProvider.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"demoInMemoryOAuthProvider.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/demoInMemoryOAuthProvider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzF,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9F,OAAgB,EAAW,QAAQ,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAKtD,qBAAa,wBAAyB,YAAW,2BAA2B;IACxE,OAAO,CAAC,OAAO,CAAiD;IAE1D,SAAS,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;IAI1B,cAAc,CAAC,cAAc,EAAE,0BAA0B;;;;;;;;;;;;;;;;;;;;;;CAIlE;AAED;;;;;;;GAOG;AACH,qBAAa,wBAAyB,YAAW,mBAAmB;IAWpD,OAAO,CAAC,gBAAgB,CAAC;IAVrC,YAAY,2BAAkC;IAC9C,OAAO,CAAC,KAAK,CAMT;IACJ,OAAO,CAAC,MAAM,CAA+B;gBAEzB,gBAAgB,CAAC,GAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,OAAO,aAAA;IAE5D,SAAS,CAAC,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCxG,6BAA6B,CAAC,MAAM,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAU7G,yBAAyB,CAC3B,MAAM,EAAE,0BAA0B,EAClC,iBAAiB,EAAE,MAAM,EAGzB,aAAa,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,WAAW,CAAC;IAoCjB,oBAAoB,CACtB,OAAO,EAAE,0BAA0B,EACnC,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,MAAM,EAAE,EAClB,SAAS,CAAC,EAAE,GAAG,GAChB,OAAO,CAAC,WAAW,CAAC;IAIjB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAc5D;AAED,eAAO,MAAM,eAAe,oDAIzB;IACC,aAAa,EAAE,GAAG,CAAC;IACnB,YAAY,EAAE,GAAG,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;CAC3B,KAAG,aA+EH,CAAC"}

View File

@@ -0,0 +1,205 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupAuthServer = exports.DemoInMemoryAuthProvider = exports.DemoInMemoryClientsStore = void 0;
const node_crypto_1 = require("node:crypto");
const express_1 = __importDefault(require("express"));
const router_js_1 = require("../../server/auth/router.js");
const auth_utils_js_1 = require("../../shared/auth-utils.js");
const errors_js_1 = require("../../server/auth/errors.js");
class DemoInMemoryClientsStore {
constructor() {
this.clients = new Map();
}
async getClient(clientId) {
return this.clients.get(clientId);
}
async registerClient(clientMetadata) {
this.clients.set(clientMetadata.client_id, clientMetadata);
return clientMetadata;
}
}
exports.DemoInMemoryClientsStore = DemoInMemoryClientsStore;
/**
* 🚨 DEMO ONLY - NOT FOR PRODUCTION
*
* This example demonstrates MCP OAuth flow but lacks some of the features required for production use,
* for example:
* - Persistent token storage
* - Rate limiting
*/
class DemoInMemoryAuthProvider {
constructor(validateResource) {
this.validateResource = validateResource;
this.clientsStore = new DemoInMemoryClientsStore();
this.codes = new Map();
this.tokens = new Map();
}
async authorize(client, params, res) {
const code = (0, node_crypto_1.randomUUID)();
const searchParams = new URLSearchParams({
code
});
if (params.state !== undefined) {
searchParams.set('state', params.state);
}
this.codes.set(code, {
client,
params
});
// Simulate a user login
// Set a secure HTTP-only session cookie with authorization info
if (res.cookie) {
const authCookieData = {
userId: 'demo_user',
name: 'Demo User',
timestamp: Date.now()
};
res.cookie('demo_session', JSON.stringify(authCookieData), {
httpOnly: true,
secure: false, // In production, this should be true
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000, // 24 hours - for demo purposes
path: '/' // Available to all routes
});
}
if (!client.redirect_uris.includes(params.redirectUri)) {
throw new errors_js_1.InvalidRequestError('Unregistered redirect_uri');
}
const targetUrl = new URL(params.redirectUri);
targetUrl.search = searchParams.toString();
res.redirect(targetUrl.toString());
}
async challengeForAuthorizationCode(client, authorizationCode) {
// Store the challenge with the code data
const codeData = this.codes.get(authorizationCode);
if (!codeData) {
throw new Error('Invalid authorization code');
}
return codeData.params.codeChallenge;
}
async exchangeAuthorizationCode(client, authorizationCode,
// Note: code verifier is checked in token.ts by default
// it's unused here for that reason.
_codeVerifier) {
const codeData = this.codes.get(authorizationCode);
if (!codeData) {
throw new Error('Invalid authorization code');
}
if (codeData.client.client_id !== client.client_id) {
throw new Error(`Authorization code was not issued to this client, ${codeData.client.client_id} != ${client.client_id}`);
}
if (this.validateResource && !this.validateResource(codeData.params.resource)) {
throw new Error(`Invalid resource: ${codeData.params.resource}`);
}
this.codes.delete(authorizationCode);
const token = (0, node_crypto_1.randomUUID)();
const tokenData = {
token,
clientId: client.client_id,
scopes: codeData.params.scopes || [],
expiresAt: Date.now() + 3600000, // 1 hour
resource: codeData.params.resource,
type: 'access'
};
this.tokens.set(token, tokenData);
return {
access_token: token,
token_type: 'bearer',
expires_in: 3600,
scope: (codeData.params.scopes || []).join(' ')
};
}
async exchangeRefreshToken(_client, _refreshToken, _scopes, _resource) {
throw new Error('Not implemented for example demo');
}
async verifyAccessToken(token) {
const tokenData = this.tokens.get(token);
if (!tokenData || !tokenData.expiresAt || tokenData.expiresAt < Date.now()) {
throw new Error('Invalid or expired token');
}
return {
token,
clientId: tokenData.clientId,
scopes: tokenData.scopes,
expiresAt: Math.floor(tokenData.expiresAt / 1000),
resource: tokenData.resource
};
}
}
exports.DemoInMemoryAuthProvider = DemoInMemoryAuthProvider;
const setupAuthServer = ({ authServerUrl, mcpServerUrl, strictResource }) => {
// Create separate auth server app
// NOTE: This is a separate app on a separate port to illustrate
// how to separate an OAuth Authorization Server from a Resource
// server in the SDK. The SDK is not intended to be provide a standalone
// authorization server.
const validateResource = strictResource
? (resource) => {
if (!resource)
return false;
const expectedResource = (0, auth_utils_js_1.resourceUrlFromServerUrl)(mcpServerUrl);
return resource.toString() === expectedResource.toString();
}
: undefined;
const provider = new DemoInMemoryAuthProvider(validateResource);
const authApp = (0, express_1.default)();
authApp.use(express_1.default.json());
// For introspection requests
authApp.use(express_1.default.urlencoded());
// Add OAuth routes to the auth server
// NOTE: this will also add a protected resource metadata route,
// but it won't be used, so leave it.
authApp.use((0, router_js_1.mcpAuthRouter)({
provider,
issuerUrl: authServerUrl,
scopesSupported: ['mcp:tools']
}));
authApp.post('/introspect', async (req, res) => {
try {
const { token } = req.body;
if (!token) {
res.status(400).json({ error: 'Token is required' });
return;
}
const tokenInfo = await provider.verifyAccessToken(token);
res.json({
active: true,
client_id: tokenInfo.clientId,
scope: tokenInfo.scopes.join(' '),
exp: tokenInfo.expiresAt,
aud: tokenInfo.resource
});
return;
}
catch (error) {
res.status(401).json({
active: false,
error: 'Unauthorized',
error_description: `Invalid token: ${error}`
});
}
});
const auth_port = authServerUrl.port;
// Start the auth server
authApp.listen(auth_port, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`OAuth Authorization Server listening on port ${auth_port}`);
});
// Note: we could fetch this from the server, but then we end up
// with some top level async which gets annoying.
const oauthMetadata = (0, router_js_1.createOAuthMetadata)({
provider,
issuerUrl: authServerUrl,
scopesSupported: ['mcp:tools']
});
oauthMetadata.introspection_endpoint = new URL('/introspect', authServerUrl).href;
return oauthMetadata;
};
exports.setupAuthServer = setupAuthServer;
//# sourceMappingURL=demoInMemoryOAuthProvider.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=elicitationFormExample.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"elicitationFormExample.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/elicitationFormExample.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,460 @@
"use strict";
// Run with: npx tsx src/examples/server/elicitationFormExample.ts
//
// This example demonstrates how to use form elicitation to collect structured user input
// with JSON Schema validation via a local HTTP server with SSE streaming.
// Form elicitation allows servers to request *non-sensitive* user input through the client
// with schema-based validation.
// Note: See also elicitationUrlExample.ts for an example of using URL elicitation
// to collect *sensitive* user input via a browser.
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const mcp_js_1 = require("../../server/mcp.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const types_js_1 = require("../../types.js");
const express_js_1 = require("../../server/express.js");
// Factory to create a new MCP server per session.
// Each session needs its own server+transport pair to avoid cross-session contamination.
const getServer = () => {
// Create MCP server - it will automatically use AjvJsonSchemaValidator with sensible defaults
// The validator supports format validation (email, date, etc.) if ajv-formats is installed
const mcpServer = new mcp_js_1.McpServer({
name: 'form-elicitation-example-server',
version: '1.0.0'
}, {
capabilities: {}
});
/**
* Example 1: Simple user registration tool
* Collects username, email, and password from the user
*/
mcpServer.registerTool('register_user', {
description: 'Register a new user account by collecting their information',
inputSchema: {}
}, async () => {
try {
// Request user information through form elicitation
const result = await mcpServer.server.elicitInput({
mode: 'form',
message: 'Please provide your registration information:',
requestedSchema: {
type: 'object',
properties: {
username: {
type: 'string',
title: 'Username',
description: 'Your desired username (3-20 characters)',
minLength: 3,
maxLength: 20
},
email: {
type: 'string',
title: 'Email',
description: 'Your email address',
format: 'email'
},
password: {
type: 'string',
title: 'Password',
description: 'Your password (min 8 characters)',
minLength: 8
},
newsletter: {
type: 'boolean',
title: 'Newsletter',
description: 'Subscribe to newsletter?',
default: false
},
role: {
type: 'string',
title: 'Role',
description: 'Your primary role',
oneOf: [
{ const: 'developer', title: 'Developer' },
{ const: 'designer', title: 'Designer' },
{ const: 'manager', title: 'Manager' },
{ const: 'other', title: 'Other' }
],
default: 'developer'
},
interests: {
type: 'array',
title: 'Interests',
description: 'Select your areas of interest',
items: {
type: 'string',
enum: ['frontend', 'backend', 'mobile', 'devops', 'ai']
},
minItems: 1,
maxItems: 3
}
},
required: ['username', 'email', 'password']
}
});
// Handle the different possible actions
if (result.action === 'accept' && result.content) {
const { username, email, newsletter } = result.content;
return {
content: [
{
type: 'text',
text: `Registration successful!\n\nUsername: ${username}\nEmail: ${email}\nNewsletter: ${newsletter ? 'Yes' : 'No'}`
}
]
};
}
else if (result.action === 'decline') {
return {
content: [
{
type: 'text',
text: 'Registration cancelled by user.'
}
]
};
}
else {
return {
content: [
{
type: 'text',
text: 'Registration was cancelled.'
}
]
};
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Registration failed: ${error instanceof Error ? error.message : String(error)}`
}
],
isError: true
};
}
});
/**
* Example 2: Multi-step workflow with multiple form elicitation requests
* Demonstrates how to collect information in multiple steps
*/
mcpServer.registerTool('create_event', {
description: 'Create a calendar event by collecting event details',
inputSchema: {}
}, async () => {
try {
// Step 1: Collect basic event information
const basicInfo = await mcpServer.server.elicitInput({
mode: 'form',
message: 'Step 1: Enter basic event information',
requestedSchema: {
type: 'object',
properties: {
title: {
type: 'string',
title: 'Event Title',
description: 'Name of the event',
minLength: 1
},
description: {
type: 'string',
title: 'Description',
description: 'Event description (optional)'
}
},
required: ['title']
}
});
if (basicInfo.action !== 'accept' || !basicInfo.content) {
return {
content: [{ type: 'text', text: 'Event creation cancelled.' }]
};
}
// Step 2: Collect date and time
const dateTime = await mcpServer.server.elicitInput({
mode: 'form',
message: 'Step 2: Enter date and time',
requestedSchema: {
type: 'object',
properties: {
date: {
type: 'string',
title: 'Date',
description: 'Event date',
format: 'date'
},
startTime: {
type: 'string',
title: 'Start Time',
description: 'Event start time (HH:MM)'
},
duration: {
type: 'integer',
title: 'Duration',
description: 'Duration in minutes',
minimum: 15,
maximum: 480
}
},
required: ['date', 'startTime', 'duration']
}
});
if (dateTime.action !== 'accept' || !dateTime.content) {
return {
content: [{ type: 'text', text: 'Event creation cancelled.' }]
};
}
// Combine all collected information
const event = {
...basicInfo.content,
...dateTime.content
};
return {
content: [
{
type: 'text',
text: `Event created successfully!\n\n${JSON.stringify(event, null, 2)}`
}
]
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Event creation failed: ${error instanceof Error ? error.message : String(error)}`
}
],
isError: true
};
}
});
/**
* Example 3: Collecting address information
* Demonstrates validation with patterns and optional fields
*/
mcpServer.registerTool('update_shipping_address', {
description: 'Update shipping address with validation',
inputSchema: {}
}, async () => {
try {
const result = await mcpServer.server.elicitInput({
mode: 'form',
message: 'Please provide your shipping address:',
requestedSchema: {
type: 'object',
properties: {
name: {
type: 'string',
title: 'Full Name',
description: 'Recipient name',
minLength: 1
},
street: {
type: 'string',
title: 'Street Address',
minLength: 1
},
city: {
type: 'string',
title: 'City',
minLength: 1
},
state: {
type: 'string',
title: 'State/Province',
minLength: 2,
maxLength: 2
},
zipCode: {
type: 'string',
title: 'ZIP/Postal Code',
description: '5-digit ZIP code'
},
phone: {
type: 'string',
title: 'Phone Number (optional)',
description: 'Contact phone number'
}
},
required: ['name', 'street', 'city', 'state', 'zipCode']
}
});
if (result.action === 'accept' && result.content) {
return {
content: [
{
type: 'text',
text: `Address updated successfully!\n\n${JSON.stringify(result.content, null, 2)}`
}
]
};
}
else if (result.action === 'decline') {
return {
content: [{ type: 'text', text: 'Address update cancelled by user.' }]
};
}
else {
return {
content: [{ type: 'text', text: 'Address update was cancelled.' }]
};
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Address update failed: ${error instanceof Error ? error.message : String(error)}`
}
],
isError: true
};
}
});
return mcpServer;
};
async function main() {
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
const app = (0, express_js_1.createMcpExpressApp)();
// Map to store transports by session ID
const transports = {};
// MCP POST endpoint
const mcpPostHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (sessionId) {
console.log(`Received MCP request for session: ${sessionId}`);
}
try {
let transport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport for this session
transport = transports[sessionId];
}
else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) {
// New initialization request - create new transport
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
onsessioninitialized: sessionId => {
// Store the transport by session ID when session is initialized
console.log(`Session initialized with ID: ${sessionId}`);
transports[sessionId] = transport;
}
});
// Set up onclose handler to clean up transport when closed
transport.onclose = () => {
const sid = transport.sessionId;
if (sid && transports[sid]) {
console.log(`Transport closed for session ${sid}, removing from transports map`);
delete transports[sid];
}
};
// Create a new server per session and connect it to the transport
const mcpServer = getServer();
await mcpServer.connect(transport);
await transport.handleRequest(req, res, req.body);
return;
}
else {
// Invalid request - no session ID or not initialization request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
});
return;
}
// Handle the request with existing transport
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
};
app.post('/mcp', mcpPostHandler);
// Handle GET requests for SSE streams
const mcpGetHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
console.log(`Establishing SSE stream for session ${sessionId}`);
const transport = transports[sessionId];
await transport.handleRequest(req, res);
};
app.get('/mcp', mcpGetHandler);
// Handle DELETE requests for session termination
const mcpDeleteHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
console.log(`Received session termination request for session ${sessionId}`);
try {
const transport = transports[sessionId];
await transport.handleRequest(req, res);
}
catch (error) {
console.error('Error handling session termination:', error);
if (!res.headersSent) {
res.status(500).send('Error processing session termination');
}
}
};
app.delete('/mcp', mcpDeleteHandler);
// Start listening
app.listen(PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`Form elicitation example server is running on http://localhost:${PORT}/mcp`);
console.log('Available tools:');
console.log(' - register_user: Collect user registration information');
console.log(' - create_event: Multi-step event creation');
console.log(' - update_shipping_address: Collect and validate address');
console.log('\nConnect your MCP client to this server using the HTTP transport.');
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
// Close all active transports to properly clean up resources
for (const sessionId in transports) {
try {
console.log(`Closing transport for session ${sessionId}`);
await transports[sessionId].close();
delete transports[sessionId];
}
catch (error) {
console.error(`Error closing transport for session ${sessionId}:`, error);
}
}
console.log('Server shutdown complete');
process.exit(0);
});
}
main().catch(error => {
console.error('Server error:', error);
process.exit(1);
});
//# sourceMappingURL=elicitationFormExample.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=elicitationUrlExample.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"elicitationUrlExample.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/elicitationUrlExample.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,656 @@
"use strict";
// Run with: npx tsx src/examples/server/elicitationUrlExample.ts
//
// This example demonstrates how to use URL elicitation to securely collect
// *sensitive* user input in a remote (HTTP) server.
// URL elicitation allows servers to prompt the end-user to open a URL in their browser
// to collect sensitive information.
// Note: See also elicitationFormExample.ts for an example of using form (not URL) elicitation
// to collect *non-sensitive* user input with a structured schema.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = __importDefault(require("express"));
const node_crypto_1 = require("node:crypto");
const zod_1 = require("zod");
const mcp_js_1 = require("../../server/mcp.js");
const express_js_1 = require("../../server/express.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const router_js_1 = require("../../server/auth/router.js");
const bearerAuth_js_1 = require("../../server/auth/middleware/bearerAuth.js");
const types_js_1 = require("../../types.js");
const inMemoryEventStore_js_1 = require("../shared/inMemoryEventStore.js");
const demoInMemoryOAuthProvider_js_1 = require("./demoInMemoryOAuthProvider.js");
const auth_utils_js_1 = require("../../shared/auth-utils.js");
const cors_1 = __importDefault(require("cors"));
// Create an MCP server with implementation details
const getServer = () => {
const mcpServer = new mcp_js_1.McpServer({
name: 'url-elicitation-http-server',
version: '1.0.0'
}, {
capabilities: { logging: {} }
});
mcpServer.registerTool('payment-confirm', {
description: 'A tool that confirms a payment directly with a user',
inputSchema: {
cartId: zod_1.z.string().describe('The ID of the cart to confirm')
}
}, async ({ cartId }, extra) => {
/*
In a real world scenario, there would be some logic here to check if the user has the provided cartId.
For the purposes of this example, we'll throw an error (-> elicits the client to open a URL to confirm payment)
*/
const sessionId = extra.sessionId;
if (!sessionId) {
throw new Error('Expected a Session ID');
}
// Create and track the elicitation
const elicitationId = generateTrackedElicitation(sessionId, elicitationId => mcpServer.server.createElicitationCompletionNotifier(elicitationId));
throw new types_js_1.UrlElicitationRequiredError([
{
mode: 'url',
message: 'This tool requires a payment confirmation. Open the link to confirm payment!',
url: `http://localhost:${MCP_PORT}/confirm-payment?session=${sessionId}&elicitation=${elicitationId}&cartId=${encodeURIComponent(cartId)}`,
elicitationId
}
]);
});
mcpServer.registerTool('third-party-auth', {
description: 'A demo tool that requires third-party OAuth credentials',
inputSchema: {
param1: zod_1.z.string().describe('First parameter')
}
}, async (_, extra) => {
/*
In a real world scenario, there would be some logic here to check if we already have a valid access token for the user.
Auth info (with a subject or `sub` claim) can be typically be found in `extra.authInfo`.
If we do, we can just return the result of the tool call.
If we don't, we can throw an ElicitationRequiredError to request the user to authenticate.
For the purposes of this example, we'll throw an error (-> elicits the client to open a URL to authenticate).
*/
const sessionId = extra.sessionId;
if (!sessionId) {
throw new Error('Expected a Session ID');
}
// Create and track the elicitation
const elicitationId = generateTrackedElicitation(sessionId, elicitationId => mcpServer.server.createElicitationCompletionNotifier(elicitationId));
// Simulate OAuth callback and token exchange after 5 seconds
// In a real app, this would be called from your OAuth callback handler
setTimeout(() => {
console.log(`Simulating OAuth token received for elicitation ${elicitationId}`);
completeURLElicitation(elicitationId);
}, 5000);
throw new types_js_1.UrlElicitationRequiredError([
{
mode: 'url',
message: 'This tool requires access to your example.com account. Open the link to authenticate!',
url: 'https://www.example.com/oauth/authorize',
elicitationId
}
]);
});
return mcpServer;
};
const elicitationsMap = new Map();
// Clean up old elicitations after 1 hour to prevent memory leaks
const ELICITATION_TTL_MS = 60 * 60 * 1000; // 1 hour
const CLEANUP_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
function cleanupOldElicitations() {
const now = new Date();
for (const [id, metadata] of elicitationsMap.entries()) {
if (now.getTime() - metadata.createdAt.getTime() > ELICITATION_TTL_MS) {
elicitationsMap.delete(id);
console.log(`Cleaned up expired elicitation: ${id}`);
}
}
}
setInterval(cleanupOldElicitations, CLEANUP_INTERVAL_MS);
/**
* Elicitation IDs must be unique strings within the MCP session
* UUIDs are used in this example for simplicity
*/
function generateElicitationId() {
return (0, node_crypto_1.randomUUID)();
}
/**
* Helper function to create and track a new elicitation.
*/
function generateTrackedElicitation(sessionId, createCompletionNotifier) {
const elicitationId = generateElicitationId();
// Create a Promise and its resolver for tracking completion
let completeResolver;
const completedPromise = new Promise(resolve => {
completeResolver = resolve;
});
const completionNotifier = createCompletionNotifier ? createCompletionNotifier(elicitationId) : undefined;
// Store the elicitation in our map
elicitationsMap.set(elicitationId, {
status: 'pending',
completedPromise,
completeResolver: completeResolver,
createdAt: new Date(),
sessionId,
completionNotifier
});
return elicitationId;
}
/**
* Helper function to complete an elicitation.
*/
function completeURLElicitation(elicitationId) {
const elicitation = elicitationsMap.get(elicitationId);
if (!elicitation) {
console.warn(`Attempted to complete unknown elicitation: ${elicitationId}`);
return;
}
if (elicitation.status === 'complete') {
console.warn(`Elicitation already complete: ${elicitationId}`);
return;
}
// Update metadata
elicitation.status = 'complete';
// Send completion notification to the client
if (elicitation.completionNotifier) {
console.log(`Sending notifications/elicitation/complete notification for elicitation ${elicitationId}`);
elicitation.completionNotifier().catch(error => {
console.error(`Failed to send completion notification for elicitation ${elicitationId}:`, error);
});
}
// Resolve the promise to unblock any waiting code
elicitation.completeResolver();
}
const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001;
const app = (0, express_js_1.createMcpExpressApp)();
// Allow CORS all domains, expose the Mcp-Session-Id header
app.use((0, cors_1.default)({
origin: '*', // Allow all origins
exposedHeaders: ['Mcp-Session-Id'],
credentials: true // Allow cookies to be sent cross-origin
}));
// Set up OAuth (required for this example)
let authMiddleware = null;
// Create auth middleware for MCP endpoints
const mcpServerUrl = new URL(`http://localhost:${MCP_PORT}/mcp`);
const authServerUrl = new URL(`http://localhost:${AUTH_PORT}`);
const oauthMetadata = (0, demoInMemoryOAuthProvider_js_1.setupAuthServer)({ authServerUrl, mcpServerUrl, strictResource: true });
const tokenVerifier = {
verifyAccessToken: async (token) => {
const endpoint = oauthMetadata.introspection_endpoint;
if (!endpoint) {
throw new Error('No token verification endpoint available in metadata');
}
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
token: token
}).toString()
});
if (!response.ok) {
const text = await response.text().catch(() => null);
throw new Error(`Invalid or expired token: ${text}`);
}
const data = await response.json();
if (!data.aud) {
throw new Error(`Resource Indicator (RFC8707) missing`);
}
if (!(0, auth_utils_js_1.checkResourceAllowed)({ requestedResource: data.aud, configuredResource: mcpServerUrl })) {
throw new Error(`Expected resource indicator ${mcpServerUrl}, got: ${data.aud}`);
}
// Convert the response to AuthInfo format
return {
token,
clientId: data.client_id,
scopes: data.scope ? data.scope.split(' ') : [],
expiresAt: data.exp
};
}
};
// Add metadata routes to the main MCP server
app.use((0, router_js_1.mcpAuthMetadataRouter)({
oauthMetadata,
resourceServerUrl: mcpServerUrl,
scopesSupported: ['mcp:tools'],
resourceName: 'MCP Demo Server'
}));
authMiddleware = (0, bearerAuth_js_1.requireBearerAuth)({
verifier: tokenVerifier,
requiredScopes: [],
resourceMetadataUrl: (0, router_js_1.getOAuthProtectedResourceMetadataUrl)(mcpServerUrl)
});
/**
* API Key Form Handling
*
* Many servers today require an API key to operate, but there's no scalable way to do this dynamically for remote servers within MCP protocol.
* URL-mode elicitation enables the server to host a simple form and get the secret data securely from the user without involving the LLM or client.
**/
async function sendApiKeyElicitation(sessionId, sender, createCompletionNotifier) {
if (!sessionId) {
console.error('No session ID provided');
throw new Error('Expected a Session ID to track elicitation');
}
console.log('🔑 URL elicitation demo: Requesting API key from client...');
const elicitationId = generateTrackedElicitation(sessionId, createCompletionNotifier);
try {
const result = await sender({
mode: 'url',
message: 'Please provide your API key to authenticate with this server',
// Host the form on the same server. In a real app, you might coordinate passing these state variables differently.
url: `http://localhost:${MCP_PORT}/api-key-form?session=${sessionId}&elicitation=${elicitationId}`,
elicitationId
});
switch (result.action) {
case 'accept':
console.log('🔑 URL elicitation demo: Client accepted the API key elicitation (now pending form submission)');
// Wait for the API key to be submitted via the form
// The form submission will complete the elicitation
break;
default:
console.log('🔑 URL elicitation demo: Client declined to provide an API key');
// In a real app, this might close the connection, but for the demo, we'll continue
break;
}
}
catch (error) {
console.error('Error during API key elicitation:', error);
}
}
// API Key Form endpoint - serves a simple HTML form
app.get('/api-key-form', (req, res) => {
const mcpSessionId = req.query.session;
const elicitationId = req.query.elicitation;
if (!mcpSessionId || !elicitationId) {
res.status(400).send('<h1>Error</h1><p>Missing required parameters</p>');
return;
}
// Check for user session cookie
// In production, this is often handled by some user auth middleware to ensure the user has a valid session
// This session is different from the MCP session.
// This userSession is the cookie that the MCP Server's Authorization Server sets for the user when they log in.
const userSession = getUserSessionCookie(req.headers.cookie);
if (!userSession) {
res.status(401).send('<h1>Error</h1><p>Unauthorized - please reconnect to login again</p>');
return;
}
// Serve a simple HTML form
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Submit Your API Key</title>
<style>
body { font-family: sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; }
input[type="text"] { width: 100%; padding: 8px; margin: 10px 0; box-sizing: border-box; }
button { background: #007bff; color: white; padding: 10px 20px; border: none; cursor: pointer; }
button:hover { background: #0056b3; }
.user { background: #d1ecf1; padding: 8px; margin-bottom: 10px; }
.info { color: #666; font-size: 0.9em; margin-top: 20px; }
</style>
</head>
<body>
<h1>API Key Required</h1>
<div class="user">✓ Logged in as: <strong>${userSession.name}</strong></div>
<form method="POST" action="/api-key-form">
<input type="hidden" name="session" value="${mcpSessionId}" />
<input type="hidden" name="elicitation" value="${elicitationId}" />
<label>API Key:<br>
<input type="text" name="apiKey" required placeholder="Enter your API key" />
</label>
<button type="submit">Submit</button>
</form>
<div class="info">This is a demo showing how a server can securely elicit sensitive data from a user using a URL.</div>
</body>
</html>
`);
});
// Handle API key form submission
app.post('/api-key-form', express_1.default.urlencoded(), (req, res) => {
const { session: sessionId, apiKey, elicitation: elicitationId } = req.body;
if (!sessionId || !apiKey || !elicitationId) {
res.status(400).send('<h1>Error</h1><p>Missing required parameters</p>');
return;
}
// Check for user session cookie here too
const userSession = getUserSessionCookie(req.headers.cookie);
if (!userSession) {
res.status(401).send('<h1>Error</h1><p>Unauthorized - please reconnect to login again</p>');
return;
}
// A real app might store this API key to be used later for the user.
console.log(`🔑 Received API key \x1b[32m${apiKey}\x1b[0m for session ${sessionId}`);
// If we have an elicitationId, complete the elicitation
completeURLElicitation(elicitationId);
// Send a success response
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Success</title>
<style>
body { font-family: sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; text-align: center; }
.success { background: #d4edda; color: #155724; padding: 20px; margin: 20px 0; }
</style>
</head>
<body>
<div class="success">
<h1>Success ✓</h1>
<p>API key received.</p>
</div>
<p>You can close this window and return to your MCP client.</p>
</body>
</html>
`);
});
// Helper to get the user session from the demo_session cookie
function getUserSessionCookie(cookieHeader) {
if (!cookieHeader)
return null;
const cookies = cookieHeader.split(';');
for (const cookie of cookies) {
const [name, value] = cookie.trim().split('=');
if (name === 'demo_session' && value) {
try {
return JSON.parse(decodeURIComponent(value));
}
catch (error) {
console.error('Failed to parse demo_session cookie:', error);
return null;
}
}
}
return null;
}
/**
* Payment Confirmation Form Handling
*
* This demonstrates how a server can use URL-mode elicitation to get user confirmation
* for sensitive operations like payment processing.
**/
// Payment Confirmation Form endpoint - serves a simple HTML form
app.get('/confirm-payment', (req, res) => {
const mcpSessionId = req.query.session;
const elicitationId = req.query.elicitation;
const cartId = req.query.cartId;
if (!mcpSessionId || !elicitationId) {
res.status(400).send('<h1>Error</h1><p>Missing required parameters</p>');
return;
}
// Check for user session cookie
// In production, this is often handled by some user auth middleware to ensure the user has a valid session
// This session is different from the MCP session.
// This userSession is the cookie that the MCP Server's Authorization Server sets for the user when they log in.
const userSession = getUserSessionCookie(req.headers.cookie);
if (!userSession) {
res.status(401).send('<h1>Error</h1><p>Unauthorized - please reconnect to login again</p>');
return;
}
// Serve a simple HTML form
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Confirm Payment</title>
<style>
body { font-family: sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; }
button { background: #28a745; color: white; padding: 12px 24px; border: none; cursor: pointer; font-size: 16px; width: 100%; margin: 10px 0; }
button:hover { background: #218838; }
button.cancel { background: #6c757d; }
button.cancel:hover { background: #5a6268; }
.user { background: #d1ecf1; padding: 8px; margin-bottom: 10px; }
.cart-info { background: #f8f9fa; padding: 12px; margin: 15px 0; border-left: 4px solid #007bff; }
.info { color: #666; font-size: 0.9em; margin-top: 20px; }
.warning { background: #fff3cd; color: #856404; padding: 12px; margin: 15px 0; border-left: 4px solid #ffc107; }
</style>
</head>
<body>
<h1>Confirm Payment</h1>
<div class="user">✓ Logged in as: <strong>${userSession.name}</strong></div>
${cartId ? `<div class="cart-info"><strong>Cart ID:</strong> ${cartId}</div>` : ''}
<div class="warning">
<strong>⚠️ Please review your order before confirming.</strong>
</div>
<form method="POST" action="/confirm-payment">
<input type="hidden" name="session" value="${mcpSessionId}" />
<input type="hidden" name="elicitation" value="${elicitationId}" />
${cartId ? `<input type="hidden" name="cartId" value="${cartId}" />` : ''}
<button type="submit" name="action" value="confirm">Confirm Payment</button>
<button type="submit" name="action" value="cancel" class="cancel">Cancel</button>
</form>
<div class="info">This is a demo showing how a server can securely get user confirmation for sensitive operations using URL-mode elicitation.</div>
</body>
</html>
`);
});
// Handle Payment Confirmation form submission
app.post('/confirm-payment', express_1.default.urlencoded(), (req, res) => {
const { session: sessionId, elicitation: elicitationId, cartId, action } = req.body;
if (!sessionId || !elicitationId) {
res.status(400).send('<h1>Error</h1><p>Missing required parameters</p>');
return;
}
// Check for user session cookie here too
const userSession = getUserSessionCookie(req.headers.cookie);
if (!userSession) {
res.status(401).send('<h1>Error</h1><p>Unauthorized - please reconnect to login again</p>');
return;
}
if (action === 'confirm') {
// A real app would process the payment here
console.log(`💳 Payment confirmed for cart ${cartId || 'unknown'} by user ${userSession.name} (session ${sessionId})`);
// Complete the elicitation
completeURLElicitation(elicitationId);
// Send a success response
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Payment Confirmed</title>
<style>
body { font-family: sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; text-align: center; }
.success { background: #d4edda; color: #155724; padding: 20px; margin: 20px 0; }
</style>
</head>
<body>
<div class="success">
<h1>Payment Confirmed ✓</h1>
<p>Your payment has been successfully processed.</p>
${cartId ? `<p><strong>Cart ID:</strong> ${cartId}</p>` : ''}
</div>
<p>You can close this window and return to your MCP client.</p>
</body>
</html>
`);
}
else if (action === 'cancel') {
console.log(`💳 Payment cancelled for cart ${cartId || 'unknown'} by user ${userSession.name} (session ${sessionId})`);
// The client will still receive a notifications/elicitation/complete notification,
// which indicates that the out-of-band interaction is complete (but not necessarily successful)
completeURLElicitation(elicitationId);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Payment Cancelled</title>
<style>
body { font-family: sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; text-align: center; }
.info { background: #d1ecf1; color: #0c5460; padding: 20px; margin: 20px 0; }
</style>
</head>
<body>
<div class="info">
<h1>Payment Cancelled</h1>
<p>Your payment has been cancelled.</p>
</div>
<p>You can close this window and return to your MCP client.</p>
</body>
</html>
`);
}
else {
res.status(400).send('<h1>Error</h1><p>Invalid action</p>');
}
});
// Map to store transports by session ID
const transports = {};
const sessionsNeedingElicitation = {};
// MCP POST endpoint
const mcpPostHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
console.debug(`Received MCP POST for session: ${sessionId || 'unknown'}`);
try {
let transport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
}
else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) {
const server = getServer();
// New initialization request
const eventStore = new inMemoryEventStore_js_1.InMemoryEventStore();
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
eventStore, // Enable resumability
onsessioninitialized: sessionId => {
// Store the transport by session ID when session is initialized
// This avoids race conditions where requests might come in before the session is stored
console.log(`Session initialized with ID: ${sessionId}`);
transports[sessionId] = transport;
sessionsNeedingElicitation[sessionId] = {
elicitationSender: params => server.server.elicitInput(params),
createCompletionNotifier: elicitationId => server.server.createElicitationCompletionNotifier(elicitationId)
};
}
});
// Set up onclose handler to clean up transport when closed
transport.onclose = () => {
const sid = transport.sessionId;
if (sid && transports[sid]) {
console.log(`Transport closed for session ${sid}, removing from transports map`);
delete transports[sid];
delete sessionsNeedingElicitation[sid];
}
};
// Connect the transport to the MCP server BEFORE handling the request
// so responses can flow back through the same transport
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
return; // Already handled
}
else {
// Invalid request - no session ID or not initialization request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
});
return;
}
// Handle the request with existing transport - no need to reconnect
// The existing transport is already connected to the server
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
};
// Set up routes with auth middleware
app.post('/mcp', authMiddleware, mcpPostHandler);
// Handle GET requests for SSE streams (using built-in support from StreamableHTTP)
const mcpGetHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
// Check for Last-Event-ID header for resumability
const lastEventId = req.headers['last-event-id'];
if (lastEventId) {
console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`);
}
else {
console.log(`Establishing new SSE stream for session ${sessionId}`);
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
if (sessionsNeedingElicitation[sessionId]) {
const { elicitationSender, createCompletionNotifier } = sessionsNeedingElicitation[sessionId];
// Send an elicitation request to the client in the background
sendApiKeyElicitation(sessionId, elicitationSender, createCompletionNotifier)
.then(() => {
// Only delete on successful send for this demo
delete sessionsNeedingElicitation[sessionId];
console.log(`🔑 URL elicitation demo: Finished sending API key elicitation request for session ${sessionId}`);
})
.catch(error => {
console.error('Error sending API key elicitation:', error);
// Keep in map to potentially retry on next reconnect
});
}
};
// Set up GET route with conditional auth middleware
app.get('/mcp', authMiddleware, mcpGetHandler);
// Handle DELETE requests for session termination (according to MCP spec)
const mcpDeleteHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
console.log(`Received session termination request for session ${sessionId}`);
try {
const transport = transports[sessionId];
await transport.handleRequest(req, res);
}
catch (error) {
console.error('Error handling session termination:', error);
if (!res.headersSent) {
res.status(500).send('Error processing session termination');
}
}
};
// Set up DELETE route with auth middleware
app.delete('/mcp', authMiddleware, mcpDeleteHandler);
app.listen(MCP_PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`MCP Streamable HTTP Server listening on port ${MCP_PORT}`);
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
// Close all active transports to properly clean up resources
for (const sessionId in transports) {
try {
console.log(`Closing transport for session ${sessionId}`);
await transports[sessionId].close();
delete transports[sessionId];
delete sessionsNeedingElicitation[sessionId];
}
catch (error) {
console.error(`Error closing transport for session ${sessionId}:`, error);
}
}
console.log('Server shutdown complete');
process.exit(0);
});
//# sourceMappingURL=elicitationUrlExample.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
/**
* Example MCP server using Hono with WebStandardStreamableHTTPServerTransport
*
* This example demonstrates using the Web Standard transport directly with Hono,
* which works on any runtime: Node.js, Cloudflare Workers, Deno, Bun, etc.
*
* Run with: npx tsx src/examples/server/honoWebStandardStreamableHttp.ts
*/
export {};
//# sourceMappingURL=honoWebStandardStreamableHttp.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"honoWebStandardStreamableHttp.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/honoWebStandardStreamableHttp.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}

View File

@@ -0,0 +1,85 @@
"use strict";
/**
* Example MCP server using Hono with WebStandardStreamableHTTPServerTransport
*
* This example demonstrates using the Web Standard transport directly with Hono,
* which works on any runtime: Node.js, Cloudflare Workers, Deno, Bun, etc.
*
* Run with: npx tsx src/examples/server/honoWebStandardStreamableHttp.ts
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const hono_1 = require("hono");
const cors_1 = require("hono/cors");
const node_server_1 = require("@hono/node-server");
const z = __importStar(require("zod/v4"));
const mcp_js_1 = require("../../server/mcp.js");
const webStandardStreamableHttp_js_1 = require("../../server/webStandardStreamableHttp.js");
// Factory function to create a new MCP server per request (stateless mode)
const getServer = () => {
const server = new mcp_js_1.McpServer({
name: 'hono-webstandard-mcp-server',
version: '1.0.0'
});
// Register a simple greeting tool
server.registerTool('greet', {
title: 'Greeting Tool',
description: 'A simple greeting tool',
inputSchema: { name: z.string().describe('Name to greet') }
}, async ({ name }) => {
return {
content: [{ type: 'text', text: `Hello, ${name}! (from Hono + WebStandard transport)` }]
};
});
return server;
};
// Create the Hono app
const app = new hono_1.Hono();
// Enable CORS for all origins
app.use('*', (0, cors_1.cors)({
origin: '*',
allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'mcp-session-id', 'Last-Event-ID', 'mcp-protocol-version'],
exposeHeaders: ['mcp-session-id', 'mcp-protocol-version']
}));
// Health check endpoint
app.get('/health', c => c.json({ status: 'ok' }));
// MCP endpoint - create a fresh transport and server per request (stateless)
app.all('/mcp', async (c) => {
const transport = new webStandardStreamableHttp_js_1.WebStandardStreamableHTTPServerTransport();
const server = getServer();
await server.connect(transport);
return transport.handleRequest(c.req.raw);
});
// Start the server
const PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
console.log(`Starting Hono MCP server on port ${PORT}`);
console.log(`Health check: http://localhost:${PORT}/health`);
console.log(`MCP endpoint: http://localhost:${PORT}/mcp`);
(0, node_server_1.serve)({
fetch: app.fetch,
port: PORT
});
//# sourceMappingURL=honoWebStandardStreamableHttp.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"honoWebStandardStreamableHttp.js","sourceRoot":"","sources":["../../../../src/examples/server/honoWebStandardStreamableHttp.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,+BAA4B;AAC5B,oCAAiC;AACjC,mDAA0C;AAC1C,0CAA4B;AAC5B,gDAAgD;AAChD,4FAAqG;AAGrG,2EAA2E;AAC3E,MAAM,SAAS,GAAG,GAAG,EAAE;IACnB,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC;QACzB,IAAI,EAAE,6BAA6B;QACnC,OAAO,EAAE,OAAO;KACnB,CAAC,CAAC;IAEH,kCAAkC;IAClC,MAAM,CAAC,YAAY,CACf,OAAO,EACP;QACI,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,wBAAwB;QACrC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE;KAC9D,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAA2B,EAAE;QACxC,OAAO;YACH,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,IAAI,uCAAuC,EAAE,CAAC;SAC3F,CAAC;IACN,CAAC,CACJ,CAAC;IAEF,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC;AAEF,sBAAsB;AACtB,MAAM,GAAG,GAAG,IAAI,WAAI,EAAE,CAAC;AAEvB,8BAA8B;AAC9B,GAAG,CAAC,GAAG,CACH,GAAG,EACH,IAAA,WAAI,EAAC;IACD,MAAM,EAAE,GAAG;IACX,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,CAAC;IAClD,YAAY,EAAE,CAAC,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,sBAAsB,CAAC;IACzF,aAAa,EAAE,CAAC,gBAAgB,EAAE,sBAAsB,CAAC;CAC5D,CAAC,CACL,CAAC;AAEF,wBAAwB;AACxB,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAElD,6EAA6E;AAC7E,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACtB,MAAM,SAAS,GAAG,IAAI,uEAAwC,EAAE,CAAC;IACjE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,mBAAmB;AACnB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAE9E,OAAO,CAAC,GAAG,CAAC,oCAAoC,IAAI,EAAE,CAAC,CAAC;AACxD,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,SAAS,CAAC,CAAC;AAC7D,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,MAAM,CAAC,CAAC;AAE1D,IAAA,mBAAK,EAAC;IACF,KAAK,EAAE,GAAG,CAAC,KAAK;IAChB,IAAI,EAAE,IAAI;CACb,CAAC,CAAC"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=jsonResponseStreamableHttp.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"jsonResponseStreamableHttp.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/jsonResponseStreamableHttp.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,171 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const mcp_js_1 = require("../../server/mcp.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const z = __importStar(require("zod/v4"));
const types_js_1 = require("../../types.js");
const express_js_1 = require("../../server/express.js");
// Create an MCP server with implementation details
const getServer = () => {
const server = new mcp_js_1.McpServer({
name: 'json-response-streamable-http-server',
version: '1.0.0'
}, {
capabilities: {
logging: {}
}
});
// Register a simple tool that returns a greeting
server.registerTool('greet', {
description: 'A simple greeting tool',
inputSchema: {
name: z.string().describe('Name to greet')
}
}, async ({ name }) => {
return {
content: [
{
type: 'text',
text: `Hello, ${name}!`
}
]
};
});
// Register a tool that sends multiple greetings with notifications
server.registerTool('multi-greet', {
description: 'A tool that sends different greetings with delays between them',
inputSchema: {
name: z.string().describe('Name to greet')
}
}, async ({ name }, extra) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
await server.sendLoggingMessage({
level: 'debug',
data: `Starting multi-greet for ${name}`
}, extra.sessionId);
await sleep(1000); // Wait 1 second before first greeting
await server.sendLoggingMessage({
level: 'info',
data: `Sending first greeting to ${name}`
}, extra.sessionId);
await sleep(1000); // Wait another second before second greeting
await server.sendLoggingMessage({
level: 'info',
data: `Sending second greeting to ${name}`
}, extra.sessionId);
return {
content: [
{
type: 'text',
text: `Good morning, ${name}!`
}
]
};
});
return server;
};
const app = (0, express_js_1.createMcpExpressApp)();
// Map to store transports by session ID
const transports = {};
app.post('/mcp', async (req, res) => {
console.log('Received MCP request:', req.body);
try {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'];
let transport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
}
else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) {
// New initialization request - use JSON response mode
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
enableJsonResponse: true, // Enable JSON response mode
onsessioninitialized: sessionId => {
// Store the transport by session ID when session is initialized
// This avoids race conditions where requests might come in before the session is stored
console.log(`Session initialized with ID: ${sessionId}`);
transports[sessionId] = transport;
}
});
// Connect the transport to the MCP server BEFORE handling the request
const server = getServer();
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
return; // Already handled
}
else {
// Invalid request - no session ID or not initialization request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
});
return;
}
// Handle the request with existing transport - no need to reconnect
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
});
// Handle GET requests for SSE streams according to spec
app.get('/mcp', async (req, res) => {
// Since this is a very simple example, we don't support GET requests for this server
// The spec requires returning 405 Method Not Allowed in this case
res.status(405).set('Allow', 'POST').send('Method Not Allowed');
});
// Start the server
const PORT = 3000;
app.listen(PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`MCP Streamable HTTP Server listening on port ${PORT}`);
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
process.exit(0);
});
//# sourceMappingURL=jsonResponseStreamableHttp.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"jsonResponseStreamableHttp.js","sourceRoot":"","sources":["../../../../src/examples/server/jsonResponseStreamableHttp.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
/**
* Example MCP server using the high-level McpServer API with outputSchema
* This demonstrates how to easily create tools with structured output
*/
export {};
//# sourceMappingURL=mcpServerOutputSchema.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"mcpServerOutputSchema.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/mcpServerOutputSchema.ts"],"names":[],"mappings":";AACA;;;GAGG"}

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env node
"use strict";
/**
* Example MCP server using the high-level McpServer API with outputSchema
* This demonstrates how to easily create tools with structured output
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const mcp_js_1 = require("../../server/mcp.js");
const stdio_js_1 = require("../../server/stdio.js");
const z = __importStar(require("zod/v4"));
const server = new mcp_js_1.McpServer({
name: 'mcp-output-schema-high-level-example',
version: '1.0.0'
});
// Define a tool with structured output - Weather data
server.registerTool('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)')
},
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 }) => {
// 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)];
const 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)]
}
};
return {
content: [
{
type: 'text',
text: JSON.stringify(structuredContent, null, 2)
}
],
structuredContent
};
});
async function main() {
const transport = new stdio_js_1.StdioServerTransport();
await server.connect(transport);
console.error('High-level Output Schema Example Server running on stdio');
}
main().catch(error => {
console.error('Server error:', error);
process.exit(1);
});
//# sourceMappingURL=mcpServerOutputSchema.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"mcpServerOutputSchema.js","sourceRoot":"","sources":["../../../../src/examples/server/mcpServerOutputSchema.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,12 @@
/**
* Example: Progress notifications over stdio.
*
* Demonstrates a tool that reports progress to the client while processing.
*
* Run:
* npx tsx src/examples/server/progressExample.ts
*
* Then connect a client with an `onprogress` callback (see docs/protocol.md).
*/
export {};
//# sourceMappingURL=progressExample.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"progressExample.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/progressExample.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}

View File

@@ -0,0 +1,49 @@
"use strict";
/**
* Example: Progress notifications over stdio.
*
* Demonstrates a tool that reports progress to the client while processing.
*
* Run:
* npx tsx src/examples/server/progressExample.ts
*
* Then connect a client with an `onprogress` callback (see docs/protocol.md).
*/
Object.defineProperty(exports, "__esModule", { value: true });
const mcp_js_1 = require("../../server/mcp.js");
const stdio_js_1 = require("../../server/stdio.js");
const zod_1 = require("zod");
const server = new mcp_js_1.McpServer({ name: 'progress-example', version: '1.0.0' }, { capabilities: { logging: {} } });
server.registerTool('count', {
description: 'Count to N with progress updates',
inputSchema: { n: zod_1.z.number().int().min(1).max(100) }
}, async ({ n }, extra) => {
for (let i = 1; i <= n; i++) {
if (extra.signal.aborted) {
return { content: [{ type: 'text', text: `Cancelled at ${i}` }], isError: true };
}
if (extra._meta?.progressToken !== undefined) {
await extra.sendNotification({
method: 'notifications/progress',
params: {
progressToken: extra._meta.progressToken,
progress: i,
total: n,
message: `Counting: ${i}/${n}`
}
});
}
// Simulate work
await new Promise(resolve => setTimeout(resolve, 100));
}
return { content: [{ type: 'text', text: `Counted to ${n}` }] };
});
async function main() {
const transport = new stdio_js_1.StdioServerTransport();
await server.connect(transport);
}
main().catch(error => {
console.error('Server error:', error);
process.exit(1);
});
//# sourceMappingURL=progressExample.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"progressExample.js","sourceRoot":"","sources":["../../../../src/examples/server/progressExample.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAEH,gDAAgD;AAChD,oDAA6D;AAC7D,6BAAwB;AAExB,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAEhH,MAAM,CAAC,YAAY,CACf,OAAO,EACP;IACI,WAAW,EAAE,kCAAkC;IAC/C,WAAW,EAAE,EAAE,CAAC,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;CACvD,EACD,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACrF,CAAC;QAED,IAAI,KAAK,CAAC,KAAK,EAAE,aAAa,KAAK,SAAS,EAAE,CAAC;YAC3C,MAAM,KAAK,CAAC,gBAAgB,CAAC;gBACzB,MAAM,EAAE,wBAAwB;gBAChC,MAAM,EAAE;oBACJ,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,aAAa;oBACxC,QAAQ,EAAE,CAAC;oBACX,KAAK,EAAE,CAAC;oBACR,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE;iBACjC;aACJ,CAAC,CAAC;QACP,CAAC;QAED,gBAAgB;QAChB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACpE,CAAC,CACJ,CAAC;AAEF,KAAK,UAAU,IAAI;IACf,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AACpC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=simpleSseServer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"simpleSseServer.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/simpleSseServer.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,168 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const mcp_js_1 = require("../../server/mcp.js");
const sse_js_1 = require("../../server/sse.js");
const z = __importStar(require("zod/v4"));
const express_js_1 = require("../../server/express.js");
/**
* This example server demonstrates the deprecated HTTP+SSE transport
* (protocol version 2024-11-05). It mainly used for testing backward compatible clients.
*
* The server exposes two endpoints:
* - /mcp: For establishing the SSE stream (GET)
* - /messages: For receiving client messages (POST)
*
*/
// Create an MCP server instance
const getServer = () => {
const server = new mcp_js_1.McpServer({
name: 'simple-sse-server',
version: '1.0.0'
}, { capabilities: { logging: {} } });
server.registerTool('start-notification-stream', {
description: 'Starts sending periodic notifications',
inputSchema: {
interval: z.number().describe('Interval in milliseconds between notifications').default(1000),
count: z.number().describe('Number of notifications to send').default(10)
}
}, async ({ interval, count }, extra) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
let counter = 0;
// Send the initial notification
await server.sendLoggingMessage({
level: 'info',
data: `Starting notification stream with ${count} messages every ${interval}ms`
}, extra.sessionId);
// Send periodic notifications
while (counter < count) {
counter++;
await sleep(interval);
try {
await server.sendLoggingMessage({
level: 'info',
data: `Notification #${counter} at ${new Date().toISOString()}`
}, extra.sessionId);
}
catch (error) {
console.error('Error sending notification:', error);
}
}
return {
content: [
{
type: 'text',
text: `Completed sending ${count} notifications every ${interval}ms`
}
]
};
});
return server;
};
const app = (0, express_js_1.createMcpExpressApp)();
// Store transports by session ID
const transports = {};
// SSE endpoint for establishing the stream
app.get('/mcp', async (req, res) => {
console.log('Received GET request to /sse (establishing SSE stream)');
try {
// Create a new SSE transport for the client
// The endpoint for POST messages is '/messages'
const transport = new sse_js_1.SSEServerTransport('/messages', res);
// Store the transport by session ID
const sessionId = transport.sessionId;
transports[sessionId] = transport;
// Set up onclose handler to clean up transport when closed
transport.onclose = () => {
console.log(`SSE transport closed for session ${sessionId}`);
delete transports[sessionId];
};
// Connect the transport to the MCP server
const server = getServer();
await server.connect(transport);
console.log(`Established SSE stream with session ID: ${sessionId}`);
}
catch (error) {
console.error('Error establishing SSE stream:', error);
if (!res.headersSent) {
res.status(500).send('Error establishing SSE stream');
}
}
});
// Messages endpoint for receiving client JSON-RPC requests
app.post('/messages', async (req, res) => {
console.log('Received POST request to /messages');
// Extract session ID from URL query parameter
// In the SSE protocol, this is added by the client based on the endpoint event
const sessionId = req.query.sessionId;
if (!sessionId) {
console.error('No session ID provided in request URL');
res.status(400).send('Missing sessionId parameter');
return;
}
const transport = transports[sessionId];
if (!transport) {
console.error(`No active transport found for session ID: ${sessionId}`);
res.status(404).send('Session not found');
return;
}
try {
// Handle the POST message with the transport
await transport.handlePostMessage(req, res, req.body);
}
catch (error) {
console.error('Error handling request:', error);
if (!res.headersSent) {
res.status(500).send('Error handling request');
}
}
});
// Start the server
const PORT = 3000;
app.listen(PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`Simple SSE Server (deprecated protocol version 2024-11-05) listening on port ${PORT}`);
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
// Close all active transports to properly clean up resources
for (const sessionId in transports) {
try {
console.log(`Closing transport for session ${sessionId}`);
await transports[sessionId].close();
delete transports[sessionId];
}
catch (error) {
console.error(`Error closing transport for session ${sessionId}:`, error);
}
}
console.log('Server shutdown complete');
process.exit(0);
});
//# sourceMappingURL=simpleSseServer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"simpleSseServer.js","sourceRoot":"","sources":["../../../../src/examples/server/simpleSseServer.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=simpleStatelessStreamableHttp.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"simpleStatelessStreamableHttp.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/simpleStatelessStreamableHttp.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,166 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const mcp_js_1 = require("../../server/mcp.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const z = __importStar(require("zod/v4"));
const express_js_1 = require("../../server/express.js");
const getServer = () => {
// Create an MCP server with implementation details
const server = new mcp_js_1.McpServer({
name: 'stateless-streamable-http-server',
version: '1.0.0'
}, { capabilities: { logging: {} } });
// Register a simple prompt
server.registerPrompt('greeting-template', {
description: 'A simple greeting prompt template',
argsSchema: {
name: z.string().describe('Name to include in greeting')
}
}, async ({ name }) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please greet ${name} in a friendly manner.`
}
}
]
};
});
// Register a tool specifically for testing resumability
server.registerTool('start-notification-stream', {
description: 'Starts sending periodic notifications for testing resumability',
inputSchema: {
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
count: z.number().describe('Number of notifications to send (0 for 100)').default(10)
}
}, async ({ interval, count }, extra) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
let counter = 0;
while (count === 0 || counter < count) {
counter++;
try {
await server.sendLoggingMessage({
level: 'info',
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
}, extra.sessionId);
}
catch (error) {
console.error('Error sending notification:', error);
}
// Wait for the specified interval
await sleep(interval);
}
return {
content: [
{
type: 'text',
text: `Started sending periodic notifications every ${interval}ms`
}
]
};
});
// Create a simple resource at a fixed URI
server.registerResource('greeting-resource', 'https://example.com/greetings/default', { mimeType: 'text/plain' }, async () => {
return {
contents: [
{
uri: 'https://example.com/greetings/default',
text: 'Hello, world!'
}
]
};
});
return server;
};
const app = (0, express_js_1.createMcpExpressApp)();
app.post('/mcp', async (req, res) => {
const server = getServer();
try {
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: undefined
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
res.on('close', () => {
console.log('Request closed');
transport.close();
server.close();
});
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
});
app.get('/mcp', async (req, res) => {
console.log('Received GET MCP request');
res.writeHead(405).end(JSON.stringify({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Method not allowed.'
},
id: null
}));
});
app.delete('/mcp', async (req, res) => {
console.log('Received DELETE MCP request');
res.writeHead(405).end(JSON.stringify({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Method not allowed.'
},
id: null
}));
});
// Start the server
const PORT = 3000;
app.listen(PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
process.exit(0);
});
//# sourceMappingURL=simpleStatelessStreamableHttp.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"simpleStatelessStreamableHttp.js","sourceRoot":"","sources":["../../../../src/examples/server/simpleStatelessStreamableHttp.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=simpleStreamableHttp.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"simpleStreamableHttp.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/simpleStreamableHttp.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,750 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const z = __importStar(require("zod/v4"));
const mcp_js_1 = require("../../server/mcp.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const router_js_1 = require("../../server/auth/router.js");
const bearerAuth_js_1 = require("../../server/auth/middleware/bearerAuth.js");
const express_js_1 = require("../../server/express.js");
const types_js_1 = require("../../types.js");
const inMemoryEventStore_js_1 = require("../shared/inMemoryEventStore.js");
const in_memory_js_1 = require("../../experimental/tasks/stores/in-memory.js");
const demoInMemoryOAuthProvider_js_1 = require("./demoInMemoryOAuthProvider.js");
const auth_utils_js_1 = require("../../shared/auth-utils.js");
// Check for OAuth flag
const useOAuth = process.argv.includes('--oauth');
const strictOAuth = process.argv.includes('--oauth-strict');
// Create shared task store for demonstration
const taskStore = new in_memory_js_1.InMemoryTaskStore();
// Create an MCP server with implementation details
const getServer = () => {
const server = new mcp_js_1.McpServer({
name: 'simple-streamable-http-server',
version: '1.0.0',
icons: [{ src: './mcp.svg', sizes: ['512x512'], mimeType: 'image/svg+xml' }],
websiteUrl: 'https://github.com/modelcontextprotocol/typescript-sdk'
}, {
capabilities: { logging: {}, tasks: { requests: { tools: { call: {} } } } },
taskStore, // Enable task support
taskMessageQueue: new in_memory_js_1.InMemoryTaskMessageQueue()
});
// Register a simple tool that returns a greeting
server.registerTool('greet', {
title: 'Greeting Tool', // Display name for UI
description: 'A simple greeting tool',
inputSchema: {
name: z.string().describe('Name to greet')
}
}, async ({ name }) => {
return {
content: [
{
type: 'text',
text: `Hello, ${name}!`
}
]
};
});
// Register a tool that sends multiple greetings with notifications (with annotations)
server.registerTool('multi-greet', {
description: 'A tool that sends different greetings with delays between them',
inputSchema: {
name: z.string().describe('Name to greet')
},
annotations: {
title: 'Multiple Greeting Tool',
readOnlyHint: true,
openWorldHint: false
}
}, async ({ name }, extra) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
await server.sendLoggingMessage({
level: 'debug',
data: `Starting multi-greet for ${name}`
}, extra.sessionId);
await sleep(1000); // Wait 1 second before first greeting
await server.sendLoggingMessage({
level: 'info',
data: `Sending first greeting to ${name}`
}, extra.sessionId);
await sleep(1000); // Wait another second before second greeting
await server.sendLoggingMessage({
level: 'info',
data: `Sending second greeting to ${name}`
}, extra.sessionId);
return {
content: [
{
type: 'text',
text: `Good morning, ${name}!`
}
]
};
});
// Register a tool that demonstrates form elicitation (user input collection with a schema)
// This creates a closure that captures the server instance
server.registerTool('collect-user-info', {
description: 'A tool that collects user information through form elicitation',
inputSchema: {
infoType: z.enum(['contact', 'preferences', 'feedback']).describe('Type of information to collect')
}
}, async ({ infoType }, extra) => {
let message;
let requestedSchema;
switch (infoType) {
case 'contact':
message = 'Please provide your contact information';
requestedSchema = {
type: 'object',
properties: {
name: {
type: 'string',
title: 'Full Name',
description: 'Your full name'
},
email: {
type: 'string',
title: 'Email Address',
description: 'Your email address',
format: 'email'
},
phone: {
type: 'string',
title: 'Phone Number',
description: 'Your phone number (optional)'
}
},
required: ['name', 'email']
};
break;
case 'preferences':
message = 'Please set your preferences';
requestedSchema = {
type: 'object',
properties: {
theme: {
type: 'string',
title: 'Theme',
description: 'Choose your preferred theme',
enum: ['light', 'dark', 'auto'],
enumNames: ['Light', 'Dark', 'Auto']
},
notifications: {
type: 'boolean',
title: 'Enable Notifications',
description: 'Would you like to receive notifications?',
default: true
},
frequency: {
type: 'string',
title: 'Notification Frequency',
description: 'How often would you like notifications?',
enum: ['daily', 'weekly', 'monthly'],
enumNames: ['Daily', 'Weekly', 'Monthly']
}
},
required: ['theme']
};
break;
case 'feedback':
message = 'Please provide your feedback';
requestedSchema = {
type: 'object',
properties: {
rating: {
type: 'integer',
title: 'Rating',
description: 'Rate your experience (1-5)',
minimum: 1,
maximum: 5
},
comments: {
type: 'string',
title: 'Comments',
description: 'Additional comments (optional)',
maxLength: 500
},
recommend: {
type: 'boolean',
title: 'Would you recommend this?',
description: 'Would you recommend this to others?'
}
},
required: ['rating', 'recommend']
};
break;
default:
throw new Error(`Unknown info type: ${infoType}`);
}
try {
// Use sendRequest through the extra parameter to elicit input
const result = await extra.sendRequest({
method: 'elicitation/create',
params: {
mode: 'form',
message,
requestedSchema
}
}, types_js_1.ElicitResultSchema);
if (result.action === 'accept') {
return {
content: [
{
type: 'text',
text: `Thank you! Collected ${infoType} information: ${JSON.stringify(result.content, null, 2)}`
}
]
};
}
else if (result.action === 'decline') {
return {
content: [
{
type: 'text',
text: `No information was collected. User declined ${infoType} information request.`
}
]
};
}
else {
return {
content: [
{
type: 'text',
text: `Information collection was cancelled by the user.`
}
]
};
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error collecting ${infoType} information: ${error}`
}
]
};
}
});
// Register a tool that demonstrates bidirectional task support:
// Server creates a task, then elicits input from client using elicitInputStream
// Using the experimental tasks API - WARNING: may change without notice
server.experimental.tasks.registerToolTask('collect-user-info-task', {
title: 'Collect Info with Task',
description: 'Collects user info via elicitation with task support using elicitInputStream',
inputSchema: {
infoType: z.enum(['contact', 'preferences']).describe('Type of information to collect').default('contact')
}
}, {
async createTask({ infoType }, { taskStore: createTaskStore, taskRequestedTtl }) {
// Create the server-side task
const task = await createTaskStore.createTask({
ttl: taskRequestedTtl
});
// Perform async work that makes a nested elicitation request using elicitInputStream
(async () => {
try {
const message = infoType === 'contact' ? 'Please provide your contact information' : 'Please set your preferences';
// Define schemas with proper typing for PrimitiveSchemaDefinition
const contactSchema = {
type: 'object',
properties: {
name: { type: 'string', title: 'Full Name', description: 'Your full name' },
email: { type: 'string', title: 'Email', description: 'Your email address' }
},
required: ['name', 'email']
};
const preferencesSchema = {
type: 'object',
properties: {
theme: { type: 'string', title: 'Theme', enum: ['light', 'dark', 'auto'] },
notifications: { type: 'boolean', title: 'Enable Notifications', default: true }
},
required: ['theme']
};
const requestedSchema = infoType === 'contact' ? contactSchema : preferencesSchema;
// Use elicitInputStream to elicit input from client
// This demonstrates the streaming elicitation API
// Access via server.server to get the underlying Server instance
const stream = server.server.experimental.tasks.elicitInputStream({
mode: 'form',
message,
requestedSchema
});
let elicitResult;
for await (const msg of stream) {
if (msg.type === 'result') {
elicitResult = msg.result;
}
else if (msg.type === 'error') {
throw msg.error;
}
}
if (!elicitResult) {
throw new Error('No result received from elicitation');
}
let resultText;
if (elicitResult.action === 'accept') {
resultText = `Collected ${infoType} info: ${JSON.stringify(elicitResult.content, null, 2)}`;
}
else if (elicitResult.action === 'decline') {
resultText = `User declined to provide ${infoType} information`;
}
else {
resultText = 'User cancelled the request';
}
await taskStore.storeTaskResult(task.taskId, 'completed', {
content: [{ type: 'text', text: resultText }]
});
}
catch (error) {
console.error('Error in collect-user-info-task:', error);
await taskStore.storeTaskResult(task.taskId, 'failed', {
content: [{ type: 'text', text: `Error: ${error}` }],
isError: true
});
}
})();
return { task };
},
async getTask(_args, { taskId, taskStore: getTaskStore }) {
return await getTaskStore.getTask(taskId);
},
async getTaskResult(_args, { taskId, taskStore: getResultTaskStore }) {
const result = await getResultTaskStore.getTaskResult(taskId);
return result;
}
});
// Register a simple prompt with title
server.registerPrompt('greeting-template', {
title: 'Greeting Template', // Display name for UI
description: 'A simple greeting prompt template',
argsSchema: {
name: z.string().describe('Name to include in greeting')
}
}, async ({ name }) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please greet ${name} in a friendly manner.`
}
}
]
};
});
// Register a tool specifically for testing resumability
server.registerTool('start-notification-stream', {
description: 'Starts sending periodic notifications for testing resumability',
inputSchema: {
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
count: z.number().describe('Number of notifications to send (0 for 100)').default(50)
}
}, async ({ interval, count }, extra) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
let counter = 0;
while (count === 0 || counter < count) {
counter++;
try {
await server.sendLoggingMessage({
level: 'info',
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
}, extra.sessionId);
}
catch (error) {
console.error('Error sending notification:', error);
}
// Wait for the specified interval
await sleep(interval);
}
return {
content: [
{
type: 'text',
text: `Started sending periodic notifications every ${interval}ms`
}
]
};
});
// Create a simple resource at a fixed URI
server.registerResource('greeting-resource', 'https://example.com/greetings/default', {
title: 'Default Greeting', // Display name for UI
description: 'A simple greeting resource',
mimeType: 'text/plain'
}, async () => {
return {
contents: [
{
uri: 'https://example.com/greetings/default',
text: 'Hello, world!'
}
]
};
});
// Create additional resources for ResourceLink demonstration
server.registerResource('example-file-1', 'file:///example/file1.txt', {
title: 'Example File 1',
description: 'First example file for ResourceLink demonstration',
mimeType: 'text/plain'
}, async () => {
return {
contents: [
{
uri: 'file:///example/file1.txt',
text: 'This is the content of file 1'
}
]
};
});
server.registerResource('example-file-2', 'file:///example/file2.txt', {
title: 'Example File 2',
description: 'Second example file for ResourceLink demonstration',
mimeType: 'text/plain'
}, async () => {
return {
contents: [
{
uri: 'file:///example/file2.txt',
text: 'This is the content of file 2'
}
]
};
});
// Register a tool that returns ResourceLinks
server.registerTool('list-files', {
title: 'List Files with ResourceLinks',
description: 'Returns a list of files as ResourceLinks without embedding their content',
inputSchema: {
includeDescriptions: z.boolean().optional().describe('Whether to include descriptions in the resource links')
}
}, async ({ includeDescriptions = true }) => {
const resourceLinks = [
{
type: 'resource_link',
uri: 'https://example.com/greetings/default',
name: 'Default Greeting',
mimeType: 'text/plain',
...(includeDescriptions && { description: 'A simple greeting resource' })
},
{
type: 'resource_link',
uri: 'file:///example/file1.txt',
name: 'Example File 1',
mimeType: 'text/plain',
...(includeDescriptions && { description: 'First example file for ResourceLink demonstration' })
},
{
type: 'resource_link',
uri: 'file:///example/file2.txt',
name: 'Example File 2',
mimeType: 'text/plain',
...(includeDescriptions && { description: 'Second example file for ResourceLink demonstration' })
}
];
return {
content: [
{
type: 'text',
text: 'Here are the available files as resource links:'
},
...resourceLinks,
{
type: 'text',
text: '\nYou can read any of these resources using their URI.'
}
]
};
});
// Register a long-running tool that demonstrates task execution
// Using the experimental tasks API - WARNING: may change without notice
server.experimental.tasks.registerToolTask('delay', {
title: 'Delay',
description: 'A simple tool that delays for a specified duration, useful for testing task execution',
inputSchema: {
duration: z.number().describe('Duration in milliseconds').default(5000)
}
}, {
async createTask({ duration }, { taskStore, taskRequestedTtl }) {
// Create the task
const task = await taskStore.createTask({
ttl: taskRequestedTtl
});
// Simulate out-of-band work
(async () => {
await new Promise(resolve => setTimeout(resolve, duration));
await taskStore.storeTaskResult(task.taskId, 'completed', {
content: [
{
type: 'text',
text: `Completed ${duration}ms delay`
}
]
});
})();
// Return CreateTaskResult with the created task
return {
task
};
},
async getTask(_args, { taskId, taskStore }) {
return await taskStore.getTask(taskId);
},
async getTaskResult(_args, { taskId, taskStore }) {
const result = await taskStore.getTaskResult(taskId);
return result;
}
});
return server;
};
const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001;
const app = (0, express_js_1.createMcpExpressApp)();
// Set up OAuth if enabled
let authMiddleware = null;
if (useOAuth) {
// Create auth middleware for MCP endpoints
const mcpServerUrl = new URL(`http://localhost:${MCP_PORT}/mcp`);
const authServerUrl = new URL(`http://localhost:${AUTH_PORT}`);
const oauthMetadata = (0, demoInMemoryOAuthProvider_js_1.setupAuthServer)({ authServerUrl, mcpServerUrl, strictResource: strictOAuth });
const tokenVerifier = {
verifyAccessToken: async (token) => {
const endpoint = oauthMetadata.introspection_endpoint;
if (!endpoint) {
throw new Error('No token verification endpoint available in metadata');
}
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
token: token
}).toString()
});
if (!response.ok) {
const text = await response.text().catch(() => null);
throw new Error(`Invalid or expired token: ${text}`);
}
const data = await response.json();
if (strictOAuth) {
if (!data.aud) {
throw new Error(`Resource Indicator (RFC8707) missing`);
}
if (!(0, auth_utils_js_1.checkResourceAllowed)({ requestedResource: data.aud, configuredResource: mcpServerUrl })) {
throw new Error(`Expected resource indicator ${mcpServerUrl}, got: ${data.aud}`);
}
}
// Convert the response to AuthInfo format
return {
token,
clientId: data.client_id,
scopes: data.scope ? data.scope.split(' ') : [],
expiresAt: data.exp
};
}
};
// Add metadata routes to the main MCP server
app.use((0, router_js_1.mcpAuthMetadataRouter)({
oauthMetadata,
resourceServerUrl: mcpServerUrl,
scopesSupported: ['mcp:tools'],
resourceName: 'MCP Demo Server'
}));
authMiddleware = (0, bearerAuth_js_1.requireBearerAuth)({
verifier: tokenVerifier,
requiredScopes: [],
resourceMetadataUrl: (0, router_js_1.getOAuthProtectedResourceMetadataUrl)(mcpServerUrl)
});
}
// Map to store transports by session ID
const transports = {};
// MCP POST endpoint with optional auth
const mcpPostHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (sessionId) {
console.log(`Received MCP request for session: ${sessionId}`);
}
else {
console.log('Request body:', req.body);
}
if (useOAuth && req.auth) {
console.log('Authenticated user:', req.auth);
}
try {
let transport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
}
else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) {
// New initialization request
const eventStore = new inMemoryEventStore_js_1.InMemoryEventStore();
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
eventStore, // Enable resumability
onsessioninitialized: sessionId => {
// Store the transport by session ID when session is initialized
// This avoids race conditions where requests might come in before the session is stored
console.log(`Session initialized with ID: ${sessionId}`);
transports[sessionId] = transport;
}
});
// Set up onclose handler to clean up transport when closed
transport.onclose = () => {
const sid = transport.sessionId;
if (sid && transports[sid]) {
console.log(`Transport closed for session ${sid}, removing from transports map`);
delete transports[sid];
}
};
// Connect the transport to the MCP server BEFORE handling the request
// so responses can flow back through the same transport
const server = getServer();
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
return; // Already handled
}
else {
// Invalid request - no session ID or not initialization request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
});
return;
}
// Handle the request with existing transport - no need to reconnect
// The existing transport is already connected to the server
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
};
// Set up routes with conditional auth middleware
if (useOAuth && authMiddleware) {
app.post('/mcp', authMiddleware, mcpPostHandler);
}
else {
app.post('/mcp', mcpPostHandler);
}
// Handle GET requests for SSE streams (using built-in support from StreamableHTTP)
const mcpGetHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
if (useOAuth && req.auth) {
console.log('Authenticated SSE connection from user:', req.auth);
}
// Check for Last-Event-ID header for resumability
const lastEventId = req.headers['last-event-id'];
if (lastEventId) {
console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`);
}
else {
console.log(`Establishing new SSE stream for session ${sessionId}`);
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
};
// Set up GET route with conditional auth middleware
if (useOAuth && authMiddleware) {
app.get('/mcp', authMiddleware, mcpGetHandler);
}
else {
app.get('/mcp', mcpGetHandler);
}
// Handle DELETE requests for session termination (according to MCP spec)
const mcpDeleteHandler = async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
console.log(`Received session termination request for session ${sessionId}`);
try {
const transport = transports[sessionId];
await transport.handleRequest(req, res);
}
catch (error) {
console.error('Error handling session termination:', error);
if (!res.headersSent) {
res.status(500).send('Error processing session termination');
}
}
};
// Set up DELETE route with conditional auth middleware
if (useOAuth && authMiddleware) {
app.delete('/mcp', authMiddleware, mcpDeleteHandler);
}
else {
app.delete('/mcp', mcpDeleteHandler);
}
app.listen(MCP_PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`MCP Streamable HTTP Server listening on port ${MCP_PORT}`);
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
// Close all active transports to properly clean up resources
for (const sessionId in transports) {
try {
console.log(`Closing transport for session ${sessionId}`);
await transports[sessionId].close();
delete transports[sessionId];
}
catch (error) {
console.error(`Error closing transport for session ${sessionId}:`, error);
}
}
console.log('Server shutdown complete');
process.exit(0);
});
//# sourceMappingURL=simpleStreamableHttp.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
/**
* Simple interactive task server demonstrating elicitation and sampling.
*
* This server demonstrates the task message queue pattern from the MCP Tasks spec:
* - confirm_delete: Uses elicitation to ask the user for confirmation
* - write_haiku: Uses sampling to request an LLM to generate content
*
* Both tools use the "call-now, fetch-later" pattern where the initial call
* creates a task, and the result is fetched via tasks/result endpoint.
*/
export {};
//# sourceMappingURL=simpleTaskInteractive.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"simpleTaskInteractive.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/simpleTaskInteractive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}

View File

@@ -0,0 +1,600 @@
"use strict";
/**
* Simple interactive task server demonstrating elicitation and sampling.
*
* This server demonstrates the task message queue pattern from the MCP Tasks spec:
* - confirm_delete: Uses elicitation to ask the user for confirmation
* - write_haiku: Uses sampling to request an LLM to generate content
*
* Both tools use the "call-now, fetch-later" pattern where the initial call
* creates a task, and the result is fetched via tasks/result endpoint.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const index_js_1 = require("../../server/index.js");
const express_js_1 = require("../../server/express.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const types_js_1 = require("../../types.js");
const interfaces_js_1 = require("../../experimental/tasks/interfaces.js");
const in_memory_js_1 = require("../../experimental/tasks/stores/in-memory.js");
// ============================================================================
// Resolver - Promise-like for passing results between async operations
// ============================================================================
class Resolver {
constructor() {
this._done = false;
this._promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
setResult(value) {
if (this._done)
return;
this._done = true;
this._resolve(value);
}
setException(error) {
if (this._done)
return;
this._done = true;
this._reject(error);
}
wait() {
return this._promise;
}
done() {
return this._done;
}
}
class TaskMessageQueueWithResolvers {
constructor() {
this.queues = new Map();
this.waitResolvers = new Map();
}
getQueue(taskId) {
let queue = this.queues.get(taskId);
if (!queue) {
queue = [];
this.queues.set(taskId, queue);
}
return queue;
}
async enqueue(taskId, message, _sessionId, maxSize) {
const queue = this.getQueue(taskId);
if (maxSize !== undefined && queue.length >= maxSize) {
throw new Error(`Task message queue overflow: queue size (${queue.length}) exceeds maximum (${maxSize})`);
}
queue.push(message);
// Notify any waiters
this.notifyWaiters(taskId);
}
async enqueueWithResolver(taskId, message, resolver, originalRequestId) {
const queue = this.getQueue(taskId);
const queuedMessage = {
type: 'request',
message,
timestamp: Date.now(),
resolver,
originalRequestId
};
queue.push(queuedMessage);
this.notifyWaiters(taskId);
}
async dequeue(taskId, _sessionId) {
const queue = this.getQueue(taskId);
return queue.shift();
}
async dequeueAll(taskId, _sessionId) {
const queue = this.queues.get(taskId) ?? [];
this.queues.delete(taskId);
return queue;
}
async waitForMessage(taskId) {
// Check if there are already messages
const queue = this.getQueue(taskId);
if (queue.length > 0)
return;
// Wait for a message to be added
return new Promise(resolve => {
let waiters = this.waitResolvers.get(taskId);
if (!waiters) {
waiters = [];
this.waitResolvers.set(taskId, waiters);
}
waiters.push(resolve);
});
}
notifyWaiters(taskId) {
const waiters = this.waitResolvers.get(taskId);
if (waiters) {
this.waitResolvers.delete(taskId);
for (const resolve of waiters) {
resolve();
}
}
}
cleanup() {
this.queues.clear();
this.waitResolvers.clear();
}
}
// ============================================================================
// Extended task store with wait functionality
// ============================================================================
class TaskStoreWithNotifications extends in_memory_js_1.InMemoryTaskStore {
constructor() {
super(...arguments);
this.updateResolvers = new Map();
}
async updateTaskStatus(taskId, status, statusMessage, sessionId) {
await super.updateTaskStatus(taskId, status, statusMessage, sessionId);
this.notifyUpdate(taskId);
}
async storeTaskResult(taskId, status, result, sessionId) {
await super.storeTaskResult(taskId, status, result, sessionId);
this.notifyUpdate(taskId);
}
async waitForUpdate(taskId) {
return new Promise(resolve => {
let waiters = this.updateResolvers.get(taskId);
if (!waiters) {
waiters = [];
this.updateResolvers.set(taskId, waiters);
}
waiters.push(resolve);
});
}
notifyUpdate(taskId) {
const waiters = this.updateResolvers.get(taskId);
if (waiters) {
this.updateResolvers.delete(taskId);
for (const resolve of waiters) {
resolve();
}
}
}
}
// ============================================================================
// Task Result Handler - delivers queued messages and routes responses
// ============================================================================
class TaskResultHandler {
constructor(store, queue) {
this.store = store;
this.queue = queue;
this.pendingRequests = new Map();
}
async handle(taskId, server, _sessionId) {
while (true) {
// Get fresh task state
const task = await this.store.getTask(taskId);
if (!task) {
throw new Error(`Task not found: ${taskId}`);
}
// Dequeue and send all pending messages
await this.deliverQueuedMessages(taskId, server, _sessionId);
// If task is terminal, return result
if ((0, interfaces_js_1.isTerminal)(task.status)) {
const result = await this.store.getTaskResult(taskId);
// Add related-task metadata per spec
return {
...result,
_meta: {
...(result._meta || {}),
[types_js_1.RELATED_TASK_META_KEY]: { taskId }
}
};
}
// Wait for task update or new message
await this.waitForUpdate(taskId);
}
}
async deliverQueuedMessages(taskId, server, _sessionId) {
while (true) {
const message = await this.queue.dequeue(taskId);
if (!message)
break;
console.log(`[Server] Delivering queued ${message.type} message for task ${taskId}`);
if (message.type === 'request') {
const reqMessage = message;
// Send the request via the server
// Store the resolver so we can route the response back
if (reqMessage.resolver && reqMessage.originalRequestId) {
this.pendingRequests.set(reqMessage.originalRequestId, reqMessage.resolver);
}
// Send the message - for elicitation/sampling, we use the server's methods
// But since we're in tasks/result context, we need to send via transport
// This is simplified - in production you'd use proper message routing
try {
const request = reqMessage.message;
let response;
if (request.method === 'elicitation/create') {
// Send elicitation request to client
const params = request.params;
response = await server.elicitInput(params);
}
else if (request.method === 'sampling/createMessage') {
// Send sampling request to client
const params = request.params;
response = await server.createMessage(params);
}
else {
throw new Error(`Unknown request method: ${request.method}`);
}
// Route response back to resolver
if (reqMessage.resolver) {
reqMessage.resolver.setResult(response);
}
}
catch (error) {
if (reqMessage.resolver) {
reqMessage.resolver.setException(error instanceof Error ? error : new Error(String(error)));
}
}
}
// For notifications, we'd send them too but this example focuses on requests
}
}
async waitForUpdate(taskId) {
// Race between store update and queue message
await Promise.race([this.store.waitForUpdate(taskId), this.queue.waitForMessage(taskId)]);
}
routeResponse(requestId, response) {
const resolver = this.pendingRequests.get(requestId);
if (resolver && !resolver.done()) {
this.pendingRequests.delete(requestId);
resolver.setResult(response);
return true;
}
return false;
}
routeError(requestId, error) {
const resolver = this.pendingRequests.get(requestId);
if (resolver && !resolver.done()) {
this.pendingRequests.delete(requestId);
resolver.setException(error);
return true;
}
return false;
}
}
// ============================================================================
// Task Session - wraps server to enqueue requests during task execution
// ============================================================================
class TaskSession {
constructor(server, taskId, store, queue) {
this.server = server;
this.taskId = taskId;
this.store = store;
this.queue = queue;
this.requestCounter = 0;
}
nextRequestId() {
return `task-${this.taskId}-${++this.requestCounter}`;
}
async elicit(message, requestedSchema) {
// Update task status to input_required
await this.store.updateTaskStatus(this.taskId, 'input_required');
const requestId = this.nextRequestId();
// Build the elicitation request with related-task metadata
const params = {
message,
requestedSchema,
mode: 'form',
_meta: {
[types_js_1.RELATED_TASK_META_KEY]: { taskId: this.taskId }
}
};
const jsonrpcRequest = {
jsonrpc: '2.0',
id: requestId,
method: 'elicitation/create',
params
};
// Create resolver to wait for response
const resolver = new Resolver();
// Enqueue the request
await this.queue.enqueueWithResolver(this.taskId, jsonrpcRequest, resolver, requestId);
try {
// Wait for response
const response = await resolver.wait();
// Update status back to working
await this.store.updateTaskStatus(this.taskId, 'working');
return response;
}
catch (error) {
await this.store.updateTaskStatus(this.taskId, 'working');
throw error;
}
}
async createMessage(messages, maxTokens) {
// Update task status to input_required
await this.store.updateTaskStatus(this.taskId, 'input_required');
const requestId = this.nextRequestId();
// Build the sampling request with related-task metadata
const params = {
messages,
maxTokens,
_meta: {
[types_js_1.RELATED_TASK_META_KEY]: { taskId: this.taskId }
}
};
const jsonrpcRequest = {
jsonrpc: '2.0',
id: requestId,
method: 'sampling/createMessage',
params
};
// Create resolver to wait for response
const resolver = new Resolver();
// Enqueue the request
await this.queue.enqueueWithResolver(this.taskId, jsonrpcRequest, resolver, requestId);
try {
// Wait for response
const response = await resolver.wait();
// Update status back to working
await this.store.updateTaskStatus(this.taskId, 'working');
return response;
}
catch (error) {
await this.store.updateTaskStatus(this.taskId, 'working');
throw error;
}
}
}
// ============================================================================
// Server Setup
// ============================================================================
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 8000;
// Create shared stores
const taskStore = new TaskStoreWithNotifications();
const messageQueue = new TaskMessageQueueWithResolvers();
const taskResultHandler = new TaskResultHandler(taskStore, messageQueue);
// Track active task executions
const activeTaskExecutions = new Map();
// Create the server
const createServer = () => {
const server = new index_js_1.Server({ name: 'simple-task-interactive', version: '1.0.0' }, {
capabilities: {
tools: {},
tasks: {
requests: {
tools: { call: {} }
}
}
}
});
// Register tools
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'confirm_delete',
description: 'Asks for confirmation before deleting (demonstrates elicitation)',
inputSchema: {
type: 'object',
properties: {
filename: { type: 'string' }
}
},
execution: { taskSupport: 'required' }
},
{
name: 'write_haiku',
description: 'Asks LLM to write a haiku (demonstrates sampling)',
inputSchema: {
type: 'object',
properties: {
topic: { type: 'string' }
}
},
execution: { taskSupport: 'required' }
}
]
};
});
// Handle tool calls
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request, extra) => {
const { name, arguments: args } = request.params;
const taskParams = (request.params._meta?.task || request.params.task);
// Validate task mode - these tools require tasks
if (!taskParams) {
throw new Error(`Tool ${name} requires task mode`);
}
// Create task
const taskOptions = {
ttl: taskParams.ttl,
pollInterval: taskParams.pollInterval ?? 1000
};
const task = await taskStore.createTask(taskOptions, extra.requestId, request, extra.sessionId);
console.log(`\n[Server] ${name} called, task created: ${task.taskId}`);
// Start background task execution
const taskExecution = (async () => {
try {
const taskSession = new TaskSession(server, task.taskId, taskStore, messageQueue);
if (name === 'confirm_delete') {
const filename = args?.filename ?? 'unknown.txt';
console.log(`[Server] confirm_delete: asking about '${filename}'`);
console.log('[Server] Sending elicitation request to client...');
const result = await taskSession.elicit(`Are you sure you want to delete '${filename}'?`, {
type: 'object',
properties: {
confirm: { type: 'boolean' }
},
required: ['confirm']
});
console.log(`[Server] Received elicitation response: action=${result.action}, content=${JSON.stringify(result.content)}`);
let text;
if (result.action === 'accept' && result.content) {
const confirmed = result.content.confirm;
text = confirmed ? `Deleted '${filename}'` : 'Deletion cancelled';
}
else {
text = 'Deletion cancelled';
}
console.log(`[Server] Completing task with result: ${text}`);
await taskStore.storeTaskResult(task.taskId, 'completed', {
content: [{ type: 'text', text }]
});
}
else if (name === 'write_haiku') {
const topic = args?.topic ?? 'nature';
console.log(`[Server] write_haiku: topic '${topic}'`);
console.log('[Server] Sending sampling request to client...');
const result = await taskSession.createMessage([
{
role: 'user',
content: { type: 'text', text: `Write a haiku about ${topic}` }
}
], 50);
let haiku = 'No response';
if (result.content && 'text' in result.content) {
haiku = result.content.text;
}
console.log(`[Server] Received sampling response: ${haiku.substring(0, 50)}...`);
console.log('[Server] Completing task with haiku');
await taskStore.storeTaskResult(task.taskId, 'completed', {
content: [{ type: 'text', text: `Haiku:\n${haiku}` }]
});
}
}
catch (error) {
console.error(`[Server] Task ${task.taskId} failed:`, error);
await taskStore.storeTaskResult(task.taskId, 'failed', {
content: [{ type: 'text', text: `Error: ${error}` }],
isError: true
});
}
finally {
activeTaskExecutions.delete(task.taskId);
}
})();
activeTaskExecutions.set(task.taskId, {
promise: taskExecution,
server,
sessionId: extra.sessionId ?? ''
});
return { task };
});
// Handle tasks/get
server.setRequestHandler(types_js_1.GetTaskRequestSchema, async (request) => {
const { taskId } = request.params;
const task = await taskStore.getTask(taskId);
if (!task) {
throw new Error(`Task ${taskId} not found`);
}
return task;
});
// Handle tasks/result
server.setRequestHandler(types_js_1.GetTaskPayloadRequestSchema, async (request, extra) => {
const { taskId } = request.params;
console.log(`[Server] tasks/result called for task ${taskId}`);
return taskResultHandler.handle(taskId, server, extra.sessionId ?? '');
});
return server;
};
// ============================================================================
// Express App Setup
// ============================================================================
const app = (0, express_js_1.createMcpExpressApp)();
// Map to store transports by session ID
const transports = {};
// Helper to check if request is initialize
const isInitializeRequest = (body) => {
return typeof body === 'object' && body !== null && 'method' in body && body.method === 'initialize';
};
// MCP POST endpoint
app.post('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
try {
let transport;
if (sessionId && transports[sessionId]) {
transport = transports[sessionId];
}
else if (!sessionId && isInitializeRequest(req.body)) {
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
onsessioninitialized: sid => {
console.log(`Session initialized: ${sid}`);
transports[sid] = transport;
}
});
transport.onclose = () => {
const sid = transport.sessionId;
if (sid && transports[sid]) {
console.log(`Transport closed for session ${sid}`);
delete transports[sid];
}
};
const server = createServer();
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
return;
}
else {
res.status(400).json({
jsonrpc: '2.0',
error: { code: -32000, message: 'Bad Request: No valid session ID' },
id: null
});
return;
}
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal server error' },
id: null
});
}
}
});
// Handle GET requests for SSE streams
app.get('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
});
// Handle DELETE requests for session termination
app.delete('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
console.log(`Session termination request: ${sessionId}`);
const transport = transports[sessionId];
await transport.handleRequest(req, res);
});
// Start server
app.listen(PORT, () => {
console.log(`Starting server on http://localhost:${PORT}/mcp`);
console.log('\nAvailable tools:');
console.log(' - confirm_delete: Demonstrates elicitation (asks user y/n)');
console.log(' - write_haiku: Demonstrates sampling (requests LLM completion)');
});
// Handle shutdown
process.on('SIGINT', async () => {
console.log('\nShutting down server...');
for (const sessionId of Object.keys(transports)) {
try {
await transports[sessionId].close();
delete transports[sessionId];
}
catch (error) {
console.error(`Error closing session ${sessionId}:`, error);
}
}
taskStore.cleanup();
messageQueue.cleanup();
console.log('Server shutdown complete');
process.exit(0);
});
//# sourceMappingURL=simpleTaskInteractive.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=sseAndStreamableHttpCompatibleServer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sseAndStreamableHttpCompatibleServer.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/sseAndStreamableHttpCompatibleServer.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,256 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const mcp_js_1 = require("../../server/mcp.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const sse_js_1 = require("../../server/sse.js");
const z = __importStar(require("zod/v4"));
const types_js_1 = require("../../types.js");
const inMemoryEventStore_js_1 = require("../shared/inMemoryEventStore.js");
const express_js_1 = require("../../server/express.js");
/**
* This example server demonstrates backwards compatibility with both:
* 1. The deprecated HTTP+SSE transport (protocol version 2024-11-05)
* 2. The Streamable HTTP transport (protocol version 2025-11-25)
*
* It maintains a single MCP server instance but exposes two transport options:
* - /mcp: The new Streamable HTTP endpoint (supports GET/POST/DELETE)
* - /sse: The deprecated SSE endpoint for older clients (GET to establish stream)
* - /messages: The deprecated POST endpoint for older clients (POST to send messages)
*/
const getServer = () => {
const server = new mcp_js_1.McpServer({
name: 'backwards-compatible-server',
version: '1.0.0'
}, { capabilities: { logging: {} } });
// Register a simple tool that sends notifications over time
server.registerTool('start-notification-stream', {
description: 'Starts sending periodic notifications for testing resumability',
inputSchema: {
interval: z.number().describe('Interval in milliseconds between notifications').default(100),
count: z.number().describe('Number of notifications to send (0 for 100)').default(50)
}
}, async ({ interval, count }, extra) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
let counter = 0;
while (count === 0 || counter < count) {
counter++;
try {
await server.sendLoggingMessage({
level: 'info',
data: `Periodic notification #${counter} at ${new Date().toISOString()}`
}, extra.sessionId);
}
catch (error) {
console.error('Error sending notification:', error);
}
// Wait for the specified interval
await sleep(interval);
}
return {
content: [
{
type: 'text',
text: `Started sending periodic notifications every ${interval}ms`
}
]
};
});
return server;
};
// Create Express application
const app = (0, express_js_1.createMcpExpressApp)();
// Store transports by session ID
const transports = {};
//=============================================================================
// STREAMABLE HTTP TRANSPORT (PROTOCOL VERSION 2025-11-25)
//=============================================================================
// Handle all MCP Streamable HTTP requests (GET, POST, DELETE) on a single endpoint
app.all('/mcp', async (req, res) => {
console.log(`Received ${req.method} request to /mcp`);
try {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'];
let transport;
if (sessionId && transports[sessionId]) {
// Check if the transport is of the correct type
const existingTransport = transports[sessionId];
if (existingTransport instanceof streamableHttp_js_1.StreamableHTTPServerTransport) {
// Reuse existing transport
transport = existingTransport;
}
else {
// Transport exists but is not a StreamableHTTPServerTransport (could be SSEServerTransport)
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: Session exists but uses a different transport protocol'
},
id: null
});
return;
}
}
else if (!sessionId && req.method === 'POST' && (0, types_js_1.isInitializeRequest)(req.body)) {
const eventStore = new inMemoryEventStore_js_1.InMemoryEventStore();
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
eventStore, // Enable resumability
onsessioninitialized: sessionId => {
// Store the transport by session ID when session is initialized
console.log(`StreamableHTTP session initialized with ID: ${sessionId}`);
transports[sessionId] = transport;
}
});
// Set up onclose handler to clean up transport when closed
transport.onclose = () => {
const sid = transport.sessionId;
if (sid && transports[sid]) {
console.log(`Transport closed for session ${sid}, removing from transports map`);
delete transports[sid];
}
};
// Connect the transport to the MCP server
const server = getServer();
await server.connect(transport);
}
else {
// Invalid request - no session ID or not initialization request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
});
return;
}
// Handle the request with the transport
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
});
//=============================================================================
// DEPRECATED HTTP+SSE TRANSPORT (PROTOCOL VERSION 2024-11-05)
//=============================================================================
app.get('/sse', async (req, res) => {
console.log('Received GET request to /sse (deprecated SSE transport)');
const transport = new sse_js_1.SSEServerTransport('/messages', res);
transports[transport.sessionId] = transport;
res.on('close', () => {
delete transports[transport.sessionId];
});
const server = getServer();
await server.connect(transport);
});
app.post('/messages', async (req, res) => {
const sessionId = req.query.sessionId;
let transport;
const existingTransport = transports[sessionId];
if (existingTransport instanceof sse_js_1.SSEServerTransport) {
// Reuse existing transport
transport = existingTransport;
}
else {
// Transport exists but is not a SSEServerTransport (could be StreamableHTTPServerTransport)
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: Session exists but uses a different transport protocol'
},
id: null
});
return;
}
if (transport) {
await transport.handlePostMessage(req, res, req.body);
}
else {
res.status(400).send('No transport found for sessionId');
}
});
// Start the server
const PORT = 3000;
app.listen(PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`Backwards compatible MCP server listening on port ${PORT}`);
console.log(`
==============================================
SUPPORTED TRANSPORT OPTIONS:
1. Streamable Http(Protocol version: 2025-11-25)
Endpoint: /mcp
Methods: GET, POST, DELETE
Usage:
- Initialize with POST to /mcp
- Establish SSE stream with GET to /mcp
- Send requests with POST to /mcp
- Terminate session with DELETE to /mcp
2. Http + SSE (Protocol version: 2024-11-05)
Endpoints: /sse (GET) and /messages (POST)
Usage:
- Establish SSE stream with GET to /sse
- Send requests with POST to /messages?sessionId=<id>
==============================================
`);
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
// Close all active transports to properly clean up resources
for (const sessionId in transports) {
try {
console.log(`Closing transport for session ${sessionId}`);
await transports[sessionId].close();
delete transports[sessionId];
}
catch (error) {
console.error(`Error closing transport for session ${sessionId}:`, error);
}
}
console.log('Server shutdown complete');
process.exit(0);
});
//# sourceMappingURL=sseAndStreamableHttpCompatibleServer.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=ssePollingExample.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ssePollingExample.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/ssePollingExample.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,107 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const mcp_js_1 = require("../../server/mcp.js");
const express_js_1 = require("../../server/express.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const inMemoryEventStore_js_1 = require("../shared/inMemoryEventStore.js");
const cors_1 = __importDefault(require("cors"));
// Factory to create a new MCP server per session.
// Each session needs its own server+transport pair to avoid cross-session contamination.
const getServer = () => {
const server = new mcp_js_1.McpServer({
name: 'sse-polling-example',
version: '1.0.0'
}, {
capabilities: { logging: {} }
});
// Register a long-running tool that demonstrates server-initiated disconnect
server.tool('long-task', 'A long-running task that sends progress updates. Server will disconnect mid-task to demonstrate polling.', {}, async (_args, extra) => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
console.log(`[${extra.sessionId}] Starting long-task...`);
// Send first progress notification
await server.sendLoggingMessage({
level: 'info',
data: 'Progress: 25% - Starting work...'
}, extra.sessionId);
await sleep(1000);
// Send second progress notification
await server.sendLoggingMessage({
level: 'info',
data: 'Progress: 50% - Halfway there...'
}, extra.sessionId);
await sleep(1000);
// Server decides to disconnect the client to free resources
// Client will reconnect via GET with Last-Event-ID after the transport's retryInterval
// Use extra.closeSSEStream callback - available when eventStore is configured
if (extra.closeSSEStream) {
console.log(`[${extra.sessionId}] Closing SSE stream to trigger client polling...`);
extra.closeSSEStream();
}
// Continue processing while client is disconnected
// Events are stored in eventStore and will be replayed on reconnect
await sleep(500);
await server.sendLoggingMessage({
level: 'info',
data: 'Progress: 75% - Almost done (sent while client disconnected)...'
}, extra.sessionId);
await sleep(500);
await server.sendLoggingMessage({
level: 'info',
data: 'Progress: 100% - Complete!'
}, extra.sessionId);
console.log(`[${extra.sessionId}] Task complete`);
return {
content: [
{
type: 'text',
text: 'Long task completed successfully!'
}
]
};
});
return server;
};
// Set up Express app
const app = (0, express_js_1.createMcpExpressApp)();
app.use((0, cors_1.default)());
// Create event store for resumability
const eventStore = new inMemoryEventStore_js_1.InMemoryEventStore();
// Track transports by session ID for session reuse
const transports = new Map();
// Handle all MCP requests
app.all('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
// Reuse existing transport or create new one
let transport = sessionId ? transports.get(sessionId) : undefined;
if (!transport) {
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
eventStore,
retryInterval: 2000, // Default retry interval for priming events
onsessioninitialized: id => {
console.log(`[${id}] Session initialized`);
transports.set(id, transport);
}
});
// Create a new server per session and connect it to the transport
const server = getServer();
await server.connect(transport);
}
await transport.handleRequest(req, res, req.body);
});
// Start the server
const PORT = 3001;
app.listen(PORT, () => {
console.log(`SSE Polling Example Server running on http://localhost:${PORT}/mcp`);
console.log('');
console.log('This server demonstrates SEP-1699 SSE polling:');
console.log('- retryInterval: 2000ms (client waits 2s before reconnecting)');
console.log('- eventStore: InMemoryEventStore (events are persisted for replay)');
console.log('');
console.log('Try calling the "long-task" tool to see server-initiated disconnect in action.');
});
//# sourceMappingURL=ssePollingExample.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ssePollingExample.js","sourceRoot":"","sources":["../../../../src/examples/server/ssePollingExample.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=standaloneSseWithGetStreamableHttp.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"standaloneSseWithGetStreamableHttp.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/standaloneSseWithGetStreamableHttp.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const mcp_js_1 = require("../../server/mcp.js");
const streamableHttp_js_1 = require("../../server/streamableHttp.js");
const types_js_1 = require("../../types.js");
const express_js_1 = require("../../server/express.js");
// Factory to create a new MCP server per session.
// Each session needs its own server+transport pair to avoid cross-session contamination.
const getServer = () => {
const server = new mcp_js_1.McpServer({
name: 'resource-list-changed-notification-server',
version: '1.0.0'
});
const addResource = (name, content) => {
const uri = `https://mcp-example.com/dynamic/${encodeURIComponent(name)}`;
server.registerResource(name, uri, { mimeType: 'text/plain', description: `Dynamic resource: ${name}` }, async () => {
return {
contents: [{ uri, text: content }]
};
});
};
addResource('example-resource', 'Initial content for example-resource');
// Periodically add new resources to demonstrate notifications
const resourceChangeInterval = setInterval(() => {
const name = (0, node_crypto_1.randomUUID)();
addResource(name, `Content for ${name}`);
}, 5000);
// Clean up the interval when the server closes
server.server.onclose = () => {
clearInterval(resourceChangeInterval);
};
return server;
};
// Store transports by session ID to send notifications
const transports = {};
const app = (0, express_js_1.createMcpExpressApp)();
app.post('/mcp', async (req, res) => {
console.log('Received MCP request:', req.body);
try {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'];
let transport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
}
else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) {
// New initialization request
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
onsessioninitialized: sessionId => {
// Store the transport by session ID when session is initialized
// This avoids race conditions where requests might come in before the session is stored
console.log(`Session initialized with ID: ${sessionId}`);
transports[sessionId] = transport;
}
});
// Create a new server per session and connect it to the transport
const server = getServer();
await server.connect(transport);
// Handle the request - the onsessioninitialized callback will store the transport
await transport.handleRequest(req, res, req.body);
return; // Already handled
}
else {
// Invalid request - no session ID or not initialization request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
});
return;
}
// Handle the request with existing transport
await transport.handleRequest(req, res, req.body);
}
catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
});
// Handle GET requests for SSE streams (now using built-in support from StreamableHTTP)
app.get('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
console.log(`Establishing SSE stream for session ${sessionId}`);
const transport = transports[sessionId];
await transport.handleRequest(req, res);
});
// Start the server
const PORT = 3000;
app.listen(PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`Server listening on port ${PORT}`);
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
for (const sessionId in transports) {
await transports[sessionId].close();
delete transports[sessionId];
}
process.exit(0);
});
//# sourceMappingURL=standaloneSseWithGetStreamableHttp.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"standaloneSseWithGetStreamableHttp.js","sourceRoot":"","sources":["../../../../src/examples/server/standaloneSseWithGetStreamableHttp.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=toolWithSampleServer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"toolWithSampleServer.d.ts","sourceRoot":"","sources":["../../../../src/examples/server/toolWithSampleServer.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,73 @@
"use strict";
// Run with: npx tsx src/examples/server/toolWithSampleServer.ts
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const mcp_js_1 = require("../../server/mcp.js");
const stdio_js_1 = require("../../server/stdio.js");
const z = __importStar(require("zod/v4"));
const mcpServer = new mcp_js_1.McpServer({
name: 'tools-with-sample-server',
version: '1.0.0'
});
// Tool that uses LLM sampling to summarize any text
mcpServer.registerTool('summarize', {
description: 'Summarize any text using an LLM',
inputSchema: {
text: z.string().describe('Text to summarize')
}
}, async ({ text }) => {
// Call the LLM through MCP sampling
const response = await mcpServer.server.createMessage({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please summarize the following text concisely:\n\n${text}`
}
}
],
maxTokens: 500
});
// Since we're not passing tools param to createMessage, response.content is single content
return {
content: [
{
type: 'text',
text: response.content.type === 'text' ? response.content.text : 'Unable to generate summary'
}
]
};
});
async function main() {
const transport = new stdio_js_1.StdioServerTransport();
await mcpServer.connect(transport);
console.log('MCP server is running...');
}
main().catch(error => {
console.error('Server error:', error);
process.exit(1);
});
//# sourceMappingURL=toolWithSampleServer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"toolWithSampleServer.js","sourceRoot":"","sources":["../../../../src/examples/server/toolWithSampleServer.ts"],"names":[],"mappings":";AAAA,gEAAgE;;;;;;;;;;;;;;;;;;;;;;;;;AAEhE,gDAAgD;AAChD,oDAA6D;AAC7D,0CAA4B;AAE5B,MAAM,SAAS,GAAG,IAAI,kBAAS,CAAC;IAC5B,IAAI,EAAE,0BAA0B;IAChC,OAAO,EAAE,OAAO;CACnB,CAAC,CAAC;AAEH,oDAAoD;AACpD,SAAS,CAAC,YAAY,CAClB,WAAW,EACX;IACI,WAAW,EAAE,iCAAiC;IAC9C,WAAW,EAAE;QACT,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;KACjD;CACJ,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;IACf,oCAAoC;IACpC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC;QAClD,QAAQ,EAAE;YACN;gBACI,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACL,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,qDAAqD,IAAI,EAAE;iBACpE;aACJ;SACJ;QACD,SAAS,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,2FAA2F;IAC3F,OAAO;QACH,OAAO,EAAE;YACL;gBACI,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,4BAA4B;aAChG;SACJ;KACJ,CAAC;AACN,CAAC,CACJ,CAAC;AAEF,KAAK,UAAU,IAAI;IACf,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAC7C,MAAM,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAC5C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}