v2 ESlint fixes (#1403)
Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
This commit is contained in:
parent
9296459ac0
commit
9fc7e28f48
22 changed files with 1672 additions and 1346 deletions
|
|
@ -37,7 +37,6 @@ import { InMemoryOAuthClientProvider } from './simpleOAuthClientProvider.js';
|
|||
// Set up OAuth (required for this example)
|
||||
const OAUTH_CALLBACK_PORT = 8090; // Use different port than auth server (3001)
|
||||
const OAUTH_CALLBACK_URL = `http://localhost:${OAUTH_CALLBACK_PORT}/callback`;
|
||||
let oauthProvider: InMemoryOAuthClientProvider | undefined = undefined;
|
||||
|
||||
console.log('Getting OAuth token...');
|
||||
const clientMetadata: OAuthClientMetadata = {
|
||||
|
|
@ -48,7 +47,7 @@ const clientMetadata: OAuthClientMetadata = {
|
|||
token_endpoint_auth_method: 'client_secret_post',
|
||||
scope: 'mcp:tools'
|
||||
};
|
||||
oauthProvider = new InMemoryOAuthClientProvider(OAUTH_CALLBACK_URL, clientMetadata, (redirectUrl: URL) => {
|
||||
const oauthProvider = new InMemoryOAuthClientProvider(OAUTH_CALLBACK_URL, clientMetadata, (redirectUrl: URL) => {
|
||||
console.log(`🌐 Opening browser for OAuth redirect: ${redirectUrl.toString()}`);
|
||||
openBrowser(redirectUrl.toString());
|
||||
});
|
||||
|
|
@ -64,7 +63,7 @@ let abortCommand = new AbortController();
|
|||
let client: Client | null = null;
|
||||
let transport: StreamableHTTPClientTransport | null = null;
|
||||
let serverUrl = 'http://localhost:3000/mcp';
|
||||
let sessionId: string | undefined = undefined;
|
||||
let sessionId: string | undefined;
|
||||
|
||||
// Elicitation queue management
|
||||
interface QueuedElicitation {
|
||||
|
|
@ -99,6 +98,7 @@ async function main(): Promise<void> {
|
|||
// Start the elicitation loop in the background
|
||||
elicitationLoop().catch(error => {
|
||||
console.error('Unexpected error in elicitation loop:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
|
@ -136,10 +136,10 @@ function printHelp(): void {
|
|||
|
||||
async function commandLoop(): Promise<void> {
|
||||
await new Promise<void>(resolve => {
|
||||
if (!isProcessingElicitations) {
|
||||
resolve();
|
||||
} else {
|
||||
if (isProcessingElicitations) {
|
||||
elicitationsCompleteSignal = resolve;
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -151,27 +151,32 @@ async function commandLoop(): Promise<void> {
|
|||
|
||||
try {
|
||||
switch (command) {
|
||||
case 'connect':
|
||||
case 'connect': {
|
||||
await connect(args[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'disconnect':
|
||||
case 'disconnect': {
|
||||
await disconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'terminate-session':
|
||||
case 'terminate-session': {
|
||||
await terminateSession();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'reconnect':
|
||||
case 'reconnect': {
|
||||
await reconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list-tools':
|
||||
case 'list-tools': {
|
||||
await listTools();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'call-tool':
|
||||
case 'call-tool': {
|
||||
if (args.length < 2) {
|
||||
console.log('Usage: call-tool <name> [args]');
|
||||
} else {
|
||||
|
|
@ -187,29 +192,35 @@ async function commandLoop(): Promise<void> {
|
|||
await callTool(toolName, toolArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'payment-confirm':
|
||||
case 'payment-confirm': {
|
||||
await callPaymentConfirmTool();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'third-party-auth':
|
||||
case 'third-party-auth': {
|
||||
await callThirdPartyAuthTool();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'help':
|
||||
case 'help': {
|
||||
printHelp();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'quit':
|
||||
case 'exit':
|
||||
case 'exit': {
|
||||
await cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
default: {
|
||||
if (command) {
|
||||
console.log(`Unknown command: ${command}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error executing command: ${error}`);
|
||||
|
|
@ -358,12 +369,12 @@ async function handleURLElicitation(params: ElicitRequestURLParams): Promise<Eli
|
|||
}
|
||||
|
||||
// Example security warning to help prevent phishing attacks
|
||||
console.log('\n⚠️ \x1b[33mSECURITY WARNING\x1b[0m ⚠️');
|
||||
console.log('\x1b[33mThe server is requesting you to open an external URL.\x1b[0m');
|
||||
console.log('\x1b[33mOnly proceed if you trust this server and understand why it needs this.\x1b[0m\n');
|
||||
console.log(`🌐 Target domain: \x1b[36m${domain}\x1b[0m`);
|
||||
console.log(`🔗 Full URL: \x1b[36m${url}\x1b[0m`);
|
||||
console.log(`\nℹ️ Server's reason:\n\n\x1b[36m${message}\x1b[0m\n`);
|
||||
console.log('\n⚠️ \u001B[33mSECURITY WARNING\u001B[0m ⚠️');
|
||||
console.log('\u001B[33mThe server is requesting you to open an external URL.\u001B[0m');
|
||||
console.log('\u001B[33mOnly proceed if you trust this server and understand why it needs this.\u001B[0m\n');
|
||||
console.log(`🌐 Target domain: \u001B[36m${domain}\u001B[0m`);
|
||||
console.log(`🔗 Full URL: \u001B[36m${url}\u001B[0m`);
|
||||
console.log(`\nℹ️ Server's reason:\n\n\u001B[36m${message}\u001B[0m\n`);
|
||||
|
||||
// 1. Ask for user consent to open the URL
|
||||
const consent = await new Promise<string>(resolve => {
|
||||
|
|
@ -386,7 +397,7 @@ async function handleURLElicitation(params: ElicitRequestURLParams): Promise<Eli
|
|||
const timeout = setTimeout(
|
||||
() => {
|
||||
pendingURLElicitations.delete(elicitationId);
|
||||
console.log(`\x1b[31m❌ Elicitation ${elicitationId} timed out waiting for completion.\x1b[0m`);
|
||||
console.log(`\u001B[31m❌ Elicitation ${elicitationId} timed out waiting for completion.\u001B[0m`);
|
||||
reject(new Error('Elicitation completion timeout'));
|
||||
},
|
||||
5 * 60 * 1000
|
||||
|
|
@ -440,7 +451,7 @@ async function waitForOAuthCallback(): Promise<string> {
|
|||
const error = parsedUrl.searchParams.get('error');
|
||||
|
||||
if (code) {
|
||||
console.log(`✅ Authorization code received: ${code?.substring(0, 10)}...`);
|
||||
console.log(`✅ Authorization code received: ${code?.slice(0, 10)}...`);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end(`
|
||||
<html>
|
||||
|
|
@ -454,7 +465,7 @@ async function waitForOAuthCallback(): Promise<string> {
|
|||
`);
|
||||
|
||||
resolve(code);
|
||||
setTimeout(() => server.close(), 15000);
|
||||
setTimeout(() => server.close(), 15_000);
|
||||
} else if (error) {
|
||||
console.log(`❌ Authorization error: ${error}`);
|
||||
res.writeHead(400, { 'Content-Type': 'text/html' });
|
||||
|
|
@ -558,7 +569,7 @@ async function connect(url?: string): Promise<void> {
|
|||
if (pending) {
|
||||
clearTimeout(pending.timeout);
|
||||
pendingURLElicitations.delete(elicitationId);
|
||||
console.log(`\x1b[32m✅ Elicitation ${elicitationId} completed!\x1b[0m`);
|
||||
console.log(`\u001B[32m✅ Elicitation ${elicitationId} completed!\u001B[0m`);
|
||||
pending.resolve();
|
||||
} else {
|
||||
// Shouldn't happen - discard it!
|
||||
|
|
@ -573,7 +584,7 @@ async function connect(url?: string): Promise<void> {
|
|||
|
||||
// Set up error handler after connection is established so we don't double log errors
|
||||
client.onerror = error => {
|
||||
console.error('\x1b[31mClient error:', error, '\x1b[0m');
|
||||
console.error('\u001B[31mClient error:', error, '\u001B[0m');
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to connect:', error);
|
||||
|
|
@ -611,7 +622,10 @@ async function terminateSession(): Promise<void> {
|
|||
console.log('Session terminated successfully');
|
||||
|
||||
// Check if sessionId was cleared after termination
|
||||
if (!transport.sessionId) {
|
||||
if (transport.sessionId) {
|
||||
console.log('Server responded with 405 Method Not Allowed (session termination not supported)');
|
||||
console.log('Session ID is still active:', transport.sessionId);
|
||||
} else {
|
||||
console.log('Session ID has been cleared');
|
||||
sessionId = undefined;
|
||||
|
||||
|
|
@ -620,9 +634,6 @@ async function terminateSession(): Promise<void> {
|
|||
console.log('Transport closed after session termination');
|
||||
client = null;
|
||||
transport = null;
|
||||
} else {
|
||||
console.log('Server responded with 405 Method Not Allowed (session termination not supported)');
|
||||
console.log('Session ID is still active:', transport.sessionId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error terminating session:', error);
|
||||
|
|
@ -683,30 +694,47 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<vo
|
|||
console.log('Tool result:');
|
||||
const resourceLinks: ResourceLink[] = [];
|
||||
|
||||
result.content.forEach(item => {
|
||||
if (item.type === 'text') {
|
||||
console.log(` ${item.text}`);
|
||||
} else if (item.type === 'resource_link') {
|
||||
const resourceLink = item as ResourceLink;
|
||||
resourceLinks.push(resourceLink);
|
||||
console.log(` 📁 Resource Link: ${resourceLink.name}`);
|
||||
console.log(` URI: ${resourceLink.uri}`);
|
||||
if (resourceLink.mimeType) {
|
||||
console.log(` Type: ${resourceLink.mimeType}`);
|
||||
for (const item of result.content) {
|
||||
switch (item.type) {
|
||||
case 'text': {
|
||||
console.log(` ${item.text}`);
|
||||
|
||||
break;
|
||||
}
|
||||
if (resourceLink.description) {
|
||||
console.log(` Description: ${resourceLink.description}`);
|
||||
case 'resource_link': {
|
||||
const resourceLink = item as ResourceLink;
|
||||
resourceLinks.push(resourceLink);
|
||||
console.log(` 📁 Resource Link: ${resourceLink.name}`);
|
||||
console.log(` URI: ${resourceLink.uri}`);
|
||||
if (resourceLink.mimeType) {
|
||||
console.log(` Type: ${resourceLink.mimeType}`);
|
||||
}
|
||||
if (resourceLink.description) {
|
||||
console.log(` Description: ${resourceLink.description}`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'resource': {
|
||||
console.log(` [Embedded Resource: ${item.resource.uri}]`);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'image': {
|
||||
console.log(` [Image: ${item.mimeType}]`);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'audio': {
|
||||
console.log(` [Audio: ${item.mimeType}]`);
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(` [Unknown content type]:`, item);
|
||||
}
|
||||
} else if (item.type === 'resource') {
|
||||
console.log(` [Embedded Resource: ${item.resource.uri}]`);
|
||||
} else if (item.type === 'image') {
|
||||
console.log(` [Image: ${item.mimeType}]`);
|
||||
} else if (item.type === 'audio') {
|
||||
console.log(` [Audio: ${item.mimeType}]`);
|
||||
} else {
|
||||
console.log(` [Unknown content type]:`, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Offer to read resource links
|
||||
if (resourceLinks.length > 0) {
|
||||
|
|
@ -749,6 +777,7 @@ async function cleanup(): Promise<void> {
|
|||
process.stdin.setRawMode(false);
|
||||
readline.close();
|
||||
console.log('\nGoodbye!');
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
|
@ -789,7 +818,10 @@ process.on('SIGINT', async () => {
|
|||
});
|
||||
|
||||
// Start the interactive client
|
||||
main().catch((error: unknown) => {
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error running MCP client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,30 +129,34 @@ async function main(): Promise<void> {
|
|||
|
||||
// Display results from all clients
|
||||
console.log('\n=== Final Results ===');
|
||||
results.forEach(({ id, result }) => {
|
||||
for (const { id, result } of results) {
|
||||
console.log(`\n[${id}] Tool result:`);
|
||||
if (Array.isArray(result.content)) {
|
||||
result.content.forEach((item: { type: string; text?: string }) => {
|
||||
for (const item of result.content) {
|
||||
if (item.type === 'text' && item.text) {
|
||||
console.log(` ${item.text}`);
|
||||
} else {
|
||||
console.log(` ${item.type} content:`, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(` Unexpected result format:`, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n=== All clients completed successfully ===');
|
||||
} catch (error) {
|
||||
console.error('Error running multiple clients:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Start the example
|
||||
main().catch((error: unknown) => {
|
||||
console.error('Error running MCP multiple clients example:', error);
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error running multiple clients:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ async function main(): Promise<void> {
|
|||
|
||||
console.log('List tools');
|
||||
const toolsRequest = await listTools(client);
|
||||
console.log('Tools: ', toolsRequest);
|
||||
console.log('Tools:', toolsRequest);
|
||||
|
||||
// 2. Start multiple notification tools in parallel
|
||||
console.log('\n=== Starting Multiple Notification Streams in Parallel ===');
|
||||
|
|
@ -59,18 +59,18 @@ async function main(): Promise<void> {
|
|||
// Log the results from each tool call
|
||||
for (const [caller, result] of Object.entries(toolResults)) {
|
||||
console.log(`\n=== Tool result for ${caller} ===`);
|
||||
result.content.forEach((item: { type: string; text?: string }) => {
|
||||
for (const item of result.content) {
|
||||
if (item.type === 'text') {
|
||||
console.log(` ${item.text}`);
|
||||
} else {
|
||||
console.log(` ${item.type} content:`, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Wait for all notifications (10 seconds)
|
||||
console.log('\n=== Waiting for all notifications ===');
|
||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||
await new Promise(resolve => setTimeout(resolve, 10_000));
|
||||
|
||||
// 4. Disconnect
|
||||
console.log('\n=== Disconnecting ===');
|
||||
|
|
@ -78,6 +78,7 @@ async function main(): Promise<void> {
|
|||
console.log('Disconnected from MCP server');
|
||||
} catch (error) {
|
||||
console.error('Error running client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -177,9 +178,9 @@ async function startParallelNotificationTools(client: Client): Promise<Record<st
|
|||
|
||||
// Organize results by caller
|
||||
const resultsByTool: Record<string, CallToolResult> = {};
|
||||
results.forEach(({ caller, result }) => {
|
||||
for (const { caller, result } of results) {
|
||||
resultsByTool[caller] = result;
|
||||
});
|
||||
}
|
||||
|
||||
return resultsByTool;
|
||||
} catch (error) {
|
||||
|
|
@ -188,8 +189,11 @@ async function startParallelNotificationTools(client: Client): Promise<Record<st
|
|||
}
|
||||
}
|
||||
|
||||
// Start the client
|
||||
main().catch((error: unknown) => {
|
||||
console.error('Error running MCP client:', error);
|
||||
try {
|
||||
// Run the client
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error running client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,10 @@ async function main() {
|
|||
await transport.close();
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error running client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class InteractiveOAuthClient {
|
|||
const error = parsedUrl.searchParams.get('error');
|
||||
|
||||
if (code) {
|
||||
console.log(`✅ Authorization code received: ${code?.substring(0, 10)}...`);
|
||||
console.log(`✅ Authorization code received: ${code?.slice(0, 10)}...`);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end(`
|
||||
<html>
|
||||
|
|
@ -256,13 +256,13 @@ class InteractiveOAuthClient {
|
|||
|
||||
if (result.tools && result.tools.length > 0) {
|
||||
console.log('\n📋 Available tools:');
|
||||
result.tools.forEach((tool, index) => {
|
||||
for (const [index, tool] of result.tools.entries()) {
|
||||
console.log(`${index + 1}. ${tool.name}`);
|
||||
if (tool.description) {
|
||||
console.log(` Description: ${tool.description}`);
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log('No tools available');
|
||||
}
|
||||
|
|
@ -314,13 +314,13 @@ class InteractiveOAuthClient {
|
|||
|
||||
console.log(`\n🔧 Tool '${toolName}' result:`);
|
||||
if (result.content) {
|
||||
result.content.forEach(content => {
|
||||
for (const content of result.content) {
|
||||
if (content.type === 'text') {
|
||||
console.log(content.text);
|
||||
} else {
|
||||
console.log(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(result);
|
||||
}
|
||||
|
|
@ -372,7 +372,7 @@ class InteractiveOAuthClient {
|
|||
{
|
||||
task: {
|
||||
taskId: `task-${Date.now()}`,
|
||||
ttl: 60000
|
||||
ttl: 60_000
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -380,32 +380,36 @@ class InteractiveOAuthClient {
|
|||
// Iterate through all messages yielded by the generator
|
||||
for await (const message of stream) {
|
||||
switch (message.type) {
|
||||
case 'taskCreated':
|
||||
case 'taskCreated': {
|
||||
console.log(`✓ Task created: ${message.task.taskId}`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'taskStatus':
|
||||
case 'taskStatus': {
|
||||
console.log(`⟳ Status: ${message.task.status}`);
|
||||
if (message.task.statusMessage) {
|
||||
console.log(` ${message.task.statusMessage}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'result':
|
||||
case 'result': {
|
||||
console.log('✓ Completed!');
|
||||
message.result.content.forEach(content => {
|
||||
for (const content of message.result.content) {
|
||||
if (content.type === 'text') {
|
||||
console.log(content.text);
|
||||
} else {
|
||||
console.log(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'error':
|
||||
case 'error': {
|
||||
console.log('✗ Error:');
|
||||
console.log(` ${message.error.message}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -456,8 +460,11 @@ async function main(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
// Run if this file is executed directly
|
||||
main().catch(error => {
|
||||
console.error('Unhandled error:', error);
|
||||
try {
|
||||
// Run if this file is executed directly
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error running client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ let notificationCount = 0;
|
|||
let client: Client | null = null;
|
||||
let transport: StreamableHTTPClientTransport | null = null;
|
||||
let serverUrl = 'http://localhost:3000/mcp';
|
||||
let notificationsToolLastEventId: string | undefined = undefined;
|
||||
let sessionId: string | undefined = undefined;
|
||||
let notificationsToolLastEventId: string | undefined;
|
||||
let sessionId: string | undefined;
|
||||
|
||||
async function main(): Promise<void> {
|
||||
console.log('MCP Interactive Client');
|
||||
|
|
@ -85,27 +85,32 @@ function commandLoop(): void {
|
|||
|
||||
try {
|
||||
switch (command) {
|
||||
case 'connect':
|
||||
case 'connect': {
|
||||
await connect(args[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'disconnect':
|
||||
case 'disconnect': {
|
||||
await disconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'terminate-session':
|
||||
case 'terminate-session': {
|
||||
await terminateSession();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'reconnect':
|
||||
case 'reconnect': {
|
||||
await reconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list-tools':
|
||||
case 'list-tools': {
|
||||
await listTools();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'call-tool':
|
||||
case 'call-tool': {
|
||||
if (args.length < 2) {
|
||||
console.log('Usage: call-tool <name> [args]');
|
||||
} else {
|
||||
|
|
@ -121,34 +126,38 @@ function commandLoop(): void {
|
|||
await callTool(toolName, toolArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'greet':
|
||||
case 'greet': {
|
||||
await callGreetTool(args[1] || 'MCP User');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'multi-greet':
|
||||
case 'multi-greet': {
|
||||
await callMultiGreetTool(args[1] || 'MCP User');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'collect-info':
|
||||
case 'collect-info': {
|
||||
await callCollectInfoTool(args[1] || 'contact');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'start-notifications': {
|
||||
const interval = args[1] ? parseInt(args[1], 10) : 2000;
|
||||
const count = args[2] ? parseInt(args[2], 10) : 10;
|
||||
const interval = args[1] ? Number.parseInt(args[1], 10) : 2000;
|
||||
const count = args[2] ? Number.parseInt(args[2], 10) : 10;
|
||||
await startNotifications(interval, count);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'run-notifications-tool-with-resumability': {
|
||||
const interval = args[1] ? parseInt(args[1], 10) : 2000;
|
||||
const count = args[2] ? parseInt(args[2], 10) : 10;
|
||||
const interval = args[1] ? Number.parseInt(args[1], 10) : 2000;
|
||||
const count = args[2] ? Number.parseInt(args[2], 10) : 10;
|
||||
await runNotificationsToolWithResumability(interval, count);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'call-tool-task':
|
||||
case 'call-tool-task': {
|
||||
if (args.length < 2) {
|
||||
console.log('Usage: call-tool-task <name> [args]');
|
||||
} else {
|
||||
|
|
@ -164,12 +173,14 @@ function commandLoop(): void {
|
|||
await callToolTask(toolName, toolArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list-prompts':
|
||||
case 'list-prompts': {
|
||||
await listPrompts();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'get-prompt':
|
||||
case 'get-prompt': {
|
||||
if (args.length < 2) {
|
||||
console.log('Usage: get-prompt <name> [args]');
|
||||
} else {
|
||||
|
|
@ -185,33 +196,39 @@ function commandLoop(): void {
|
|||
await getPrompt(promptName, promptArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list-resources':
|
||||
case 'list-resources': {
|
||||
await listResources();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'read-resource':
|
||||
case 'read-resource': {
|
||||
if (args.length < 2) {
|
||||
console.log('Usage: read-resource <uri>');
|
||||
} else {
|
||||
await readResource(args[1]!);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'help':
|
||||
case 'help': {
|
||||
printHelp();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'quit':
|
||||
case 'exit':
|
||||
case 'exit': {
|
||||
await cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
default: {
|
||||
if (command) {
|
||||
console.log(`Unknown command: ${command}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error executing command: ${error}`);
|
||||
|
|
@ -250,7 +267,7 @@ async function connect(url?: string): Promise<void> {
|
|||
}
|
||||
);
|
||||
client.onerror = error => {
|
||||
console.error('\x1b[31mClient error:', error, '\x1b[0m');
|
||||
console.error('\u001B[31mClient error:', error, '\u001B[0m');
|
||||
};
|
||||
|
||||
// Set up elicitation request handler with proper validation
|
||||
|
|
@ -353,25 +370,38 @@ async function connect(url?: string): Promise<void> {
|
|||
// Parse the value based on type
|
||||
let parsedValue: unknown;
|
||||
|
||||
if (field.type === 'boolean') {
|
||||
parsedValue = answer.toLowerCase() === 'true' || answer.toLowerCase() === 'yes' || answer === '1';
|
||||
} else if (field.type === 'number') {
|
||||
parsedValue = parseFloat(answer);
|
||||
if (isNaN(parsedValue as number)) {
|
||||
throw new Error(`${fieldName} must be a valid number`);
|
||||
switch (field.type) {
|
||||
case 'boolean': {
|
||||
parsedValue = answer.toLowerCase() === 'true' || answer.toLowerCase() === 'yes' || answer === '1';
|
||||
|
||||
break;
|
||||
}
|
||||
} else if (field.type === 'integer') {
|
||||
parsedValue = parseInt(answer, 10);
|
||||
if (isNaN(parsedValue as number)) {
|
||||
throw new Error(`${fieldName} must be a valid integer`);
|
||||
case 'number': {
|
||||
parsedValue = Number.parseFloat(answer);
|
||||
if (Number.isNaN(parsedValue as number)) {
|
||||
throw new TypeError(`${fieldName} must be a valid number`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} else if (field.enum) {
|
||||
if (!field.enum.includes(answer)) {
|
||||
throw new Error(`${fieldName} must be one of: ${field.enum.join(', ')}`);
|
||||
case 'integer': {
|
||||
parsedValue = Number.parseInt(answer, 10);
|
||||
if (Number.isNaN(parsedValue as number)) {
|
||||
throw new TypeError(`${fieldName} must be a valid integer`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (field.enum) {
|
||||
if (!field.enum.includes(answer)) {
|
||||
throw new Error(`${fieldName} must be one of: ${field.enum.join(', ')}`);
|
||||
}
|
||||
parsedValue = answer;
|
||||
} else {
|
||||
parsedValue = answer;
|
||||
}
|
||||
}
|
||||
parsedValue = answer;
|
||||
} else {
|
||||
parsedValue = answer;
|
||||
}
|
||||
|
||||
content[fieldName] = parsedValue;
|
||||
|
|
@ -406,9 +436,10 @@ async function connect(url?: string): Promise<void> {
|
|||
|
||||
if (!isValid) {
|
||||
console.log('❌ Validation errors:');
|
||||
validate.errors?.forEach(error => {
|
||||
console.log(` - ${error.instancePath || 'root'}: ${error.message}`);
|
||||
});
|
||||
if (validate.errors)
|
||||
for (const error of validate.errors) {
|
||||
console.log(` - ${error.instancePath || 'root'}: ${error.message}`);
|
||||
}
|
||||
|
||||
if (attempts < maxAttempts) {
|
||||
console.log('Please correct the errors and try again...');
|
||||
|
|
@ -429,20 +460,30 @@ async function connect(url?: string): Promise<void> {
|
|||
});
|
||||
});
|
||||
|
||||
if (confirmAnswer === 'yes' || confirmAnswer === 'y') {
|
||||
return {
|
||||
action: 'accept',
|
||||
content
|
||||
};
|
||||
} else if (confirmAnswer === 'cancel' || confirmAnswer === 'c') {
|
||||
return { action: 'cancel' };
|
||||
} else if (confirmAnswer === 'no' || confirmAnswer === 'n') {
|
||||
if (attempts < maxAttempts) {
|
||||
console.log('Please re-enter the information...');
|
||||
continue;
|
||||
} else {
|
||||
return { action: 'decline' };
|
||||
switch (confirmAnswer) {
|
||||
case 'yes':
|
||||
case 'y': {
|
||||
return {
|
||||
action: 'accept',
|
||||
content
|
||||
};
|
||||
}
|
||||
case 'cancel':
|
||||
case 'c': {
|
||||
return { action: 'cancel' };
|
||||
}
|
||||
case 'no':
|
||||
case 'n': {
|
||||
if (attempts < maxAttempts) {
|
||||
console.log('Please re-enter the information...');
|
||||
continue;
|
||||
} else {
|
||||
return { action: 'decline' };
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// No default
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -524,7 +565,10 @@ async function terminateSession(): Promise<void> {
|
|||
console.log('Session terminated successfully');
|
||||
|
||||
// Check if sessionId was cleared after termination
|
||||
if (!transport.sessionId) {
|
||||
if (transport.sessionId) {
|
||||
console.log('Server responded with 405 Method Not Allowed (session termination not supported)');
|
||||
console.log('Session ID is still active:', transport.sessionId);
|
||||
} else {
|
||||
console.log('Session ID has been cleared');
|
||||
sessionId = undefined;
|
||||
|
||||
|
|
@ -533,9 +577,6 @@ async function terminateSession(): Promise<void> {
|
|||
console.log('Transport closed after session termination');
|
||||
client = null;
|
||||
transport = null;
|
||||
} else {
|
||||
console.log('Server responded with 405 Method Not Allowed (session termination not supported)');
|
||||
console.log('Session ID is still active:', transport.sessionId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error terminating session:', error);
|
||||
|
|
@ -596,30 +637,47 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<vo
|
|||
console.log('Tool result:');
|
||||
const resourceLinks: ResourceLink[] = [];
|
||||
|
||||
result.content.forEach(item => {
|
||||
if (item.type === 'text') {
|
||||
console.log(` ${item.text}`);
|
||||
} else if (item.type === 'resource_link') {
|
||||
const resourceLink = item as ResourceLink;
|
||||
resourceLinks.push(resourceLink);
|
||||
console.log(` 📁 Resource Link: ${resourceLink.name}`);
|
||||
console.log(` URI: ${resourceLink.uri}`);
|
||||
if (resourceLink.mimeType) {
|
||||
console.log(` Type: ${resourceLink.mimeType}`);
|
||||
for (const item of result.content) {
|
||||
switch (item.type) {
|
||||
case 'text': {
|
||||
console.log(` ${item.text}`);
|
||||
|
||||
break;
|
||||
}
|
||||
if (resourceLink.description) {
|
||||
console.log(` Description: ${resourceLink.description}`);
|
||||
case 'resource_link': {
|
||||
const resourceLink = item as ResourceLink;
|
||||
resourceLinks.push(resourceLink);
|
||||
console.log(` 📁 Resource Link: ${resourceLink.name}`);
|
||||
console.log(` URI: ${resourceLink.uri}`);
|
||||
if (resourceLink.mimeType) {
|
||||
console.log(` Type: ${resourceLink.mimeType}`);
|
||||
}
|
||||
if (resourceLink.description) {
|
||||
console.log(` Description: ${resourceLink.description}`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'resource': {
|
||||
console.log(` [Embedded Resource: ${item.resource.uri}]`);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'image': {
|
||||
console.log(` [Image: ${item.mimeType}]`);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'audio': {
|
||||
console.log(` [Audio: ${item.mimeType}]`);
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(` [Unknown content type]:`, item);
|
||||
}
|
||||
} else if (item.type === 'resource') {
|
||||
console.log(` [Embedded Resource: ${item.resource.uri}]`);
|
||||
} else if (item.type === 'image') {
|
||||
console.log(` [Image: ${item.mimeType}]`);
|
||||
} else if (item.type === 'audio') {
|
||||
console.log(` [Audio: ${item.mimeType}]`);
|
||||
} else {
|
||||
console.log(` [Unknown content type]:`, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Offer to read resource links
|
||||
if (resourceLinks.length > 0) {
|
||||
|
|
@ -678,13 +736,13 @@ async function runNotificationsToolWithResumability(interval: number, count: num
|
|||
});
|
||||
|
||||
console.log('Tool result:');
|
||||
result.content.forEach(item => {
|
||||
for (const item of result.content) {
|
||||
if (item.type === 'text') {
|
||||
console.log(` ${item.text}`);
|
||||
} else {
|
||||
console.log(` ${item.type} content:`, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error starting notification stream: ${error}`);
|
||||
}
|
||||
|
|
@ -732,9 +790,9 @@ async function getPrompt(name: string, args: Record<string, unknown>): Promise<v
|
|||
|
||||
const promptResult = await client.request(promptRequest, GetPromptResultSchema);
|
||||
console.log('Prompt template:');
|
||||
promptResult.messages.forEach((msg, index) => {
|
||||
for (const [index, msg] of promptResult.messages.entries()) {
|
||||
console.log(` [${index + 1}] ${msg.role}: ${msg.content.type === 'text' ? msg.content.text : JSON.stringify(msg.content)}`);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error getting prompt ${name}: ${error}`);
|
||||
}
|
||||
|
|
@ -830,7 +888,7 @@ async function callToolTask(name: string, args: Record<string, unknown>): Promis
|
|||
CallToolResultSchema,
|
||||
{
|
||||
task: {
|
||||
ttl: 60000 // Keep results for 60 seconds
|
||||
ttl: 60_000 // Keep results for 60 seconds
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -840,26 +898,30 @@ async function callToolTask(name: string, args: Record<string, unknown>): Promis
|
|||
let lastStatus = '';
|
||||
for await (const message of stream) {
|
||||
switch (message.type) {
|
||||
case 'taskCreated':
|
||||
case 'taskCreated': {
|
||||
console.log('Task created successfully with ID:', message.task.taskId);
|
||||
break;
|
||||
case 'taskStatus':
|
||||
}
|
||||
case 'taskStatus': {
|
||||
if (lastStatus !== message.task.status) {
|
||||
console.log(` ${message.task.status}${message.task.statusMessage ? ` - ${message.task.statusMessage}` : ''}`);
|
||||
}
|
||||
lastStatus = message.task.status;
|
||||
break;
|
||||
case 'result':
|
||||
}
|
||||
case 'result': {
|
||||
console.log('Task completed!');
|
||||
console.log('Tool result:');
|
||||
message.result.content.forEach(item => {
|
||||
for (const item of message.result.content) {
|
||||
if (item.type === 'text') {
|
||||
console.log(` ${item.text}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'error':
|
||||
}
|
||||
case 'error': {
|
||||
throw message.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -891,6 +953,7 @@ async function cleanup(): Promise<void> {
|
|||
process.stdin.setRawMode(false);
|
||||
readline.close();
|
||||
console.log('\nGoodbye!');
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
|
@ -921,7 +984,10 @@ process.on('SIGINT', async () => {
|
|||
});
|
||||
|
||||
// Start the interactive client
|
||||
main().catch((error: unknown) => {
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error running MCP client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,23 +130,27 @@ async function run(url: string): Promise<void> {
|
|||
const confirmStream = client.experimental.tasks.callToolStream(
|
||||
{ name: 'confirm_delete', arguments: { filename: 'important.txt' } },
|
||||
CallToolResultSchema,
|
||||
{ task: { ttl: 60000 } }
|
||||
{ task: { ttl: 60_000 } }
|
||||
);
|
||||
|
||||
for await (const message of confirmStream) {
|
||||
switch (message.type) {
|
||||
case 'taskCreated':
|
||||
case 'taskCreated': {
|
||||
console.log(`Task created: ${message.task.taskId}`);
|
||||
break;
|
||||
case 'taskStatus':
|
||||
}
|
||||
case 'taskStatus': {
|
||||
console.log(`Task status: ${message.task.status}`);
|
||||
break;
|
||||
case 'result':
|
||||
}
|
||||
case 'result': {
|
||||
console.log(`Result: ${getTextContent(message.result)}`);
|
||||
break;
|
||||
case 'error':
|
||||
}
|
||||
case 'error': {
|
||||
console.error(`Error: ${message.error}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,24 +162,28 @@ async function run(url: string): Promise<void> {
|
|||
{ name: 'write_haiku', arguments: { topic: 'autumn leaves' } },
|
||||
CallToolResultSchema,
|
||||
{
|
||||
task: { ttl: 60000 }
|
||||
task: { ttl: 60_000 }
|
||||
}
|
||||
);
|
||||
|
||||
for await (const message of haikuStream) {
|
||||
switch (message.type) {
|
||||
case 'taskCreated':
|
||||
case 'taskCreated': {
|
||||
console.log(`Task created: ${message.task.taskId}`);
|
||||
break;
|
||||
case 'taskStatus':
|
||||
}
|
||||
case 'taskStatus': {
|
||||
console.log(`Task status: ${message.task.status}`);
|
||||
break;
|
||||
case 'result':
|
||||
}
|
||||
case 'result': {
|
||||
console.log(`Result:\n${getTextContent(message.result)}`);
|
||||
break;
|
||||
case 'error':
|
||||
}
|
||||
case 'error': {
|
||||
console.error(`Error: ${message.error}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +205,10 @@ for (let i = 0; i < args.length; i++) {
|
|||
}
|
||||
|
||||
// Run the client
|
||||
run(url).catch(error => {
|
||||
try {
|
||||
await run(url);
|
||||
} catch (error) {
|
||||
console.error('Error running client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,4 +106,10 @@ async function main(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error running MCP client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ async function main(): Promise<void> {
|
|||
console.log('Disconnected from MCP server');
|
||||
} catch (error) {
|
||||
console.error('Error running client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -171,20 +172,23 @@ async function startNotificationTool(client: Client): Promise<void> {
|
|||
const result = await client.request(request, CallToolResultSchema);
|
||||
|
||||
console.log('Tool result:');
|
||||
result.content.forEach(item => {
|
||||
for (const item of result.content) {
|
||||
if (item.type === 'text') {
|
||||
console.log(` ${item.text}`);
|
||||
} else {
|
||||
console.log(` ${item.type} content:`, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error calling notification tool: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Start the client
|
||||
main().catch((error: unknown) => {
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error running MCP client:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ mcpServer.registerTool(
|
|||
);
|
||||
|
||||
async function main() {
|
||||
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
|
||||
const PORT = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3000;
|
||||
|
||||
const app = createMcpExpressApp();
|
||||
|
||||
|
|
@ -367,7 +367,7 @@ async function main() {
|
|||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
code: -32_000,
|
||||
message: 'Bad Request: No valid session ID provided'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -383,7 +383,7 @@ async function main() {
|
|||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
code: -32_603,
|
||||
message: 'Internal server error'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -436,6 +436,7 @@ async function main() {
|
|||
app.listen(PORT, error => {
|
||||
if (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Form elicitation example server is running on http://localhost:${PORT}/mcp`);
|
||||
|
|
@ -465,7 +466,10 @@ async function main() {
|
|||
});
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,8 +215,8 @@ function completeURLElicitation(elicitationId: string) {
|
|||
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 MCP_PORT = process.env.MCP_PORT ? Number.parseInt(process.env.MCP_PORT, 10) : 3000;
|
||||
const AUTH_PORT = process.env.MCP_AUTH_PORT ? Number.parseInt(process.env.MCP_AUTH_PORT, 10) : 3001;
|
||||
|
||||
const app = createMcpExpressApp();
|
||||
|
||||
|
|
@ -277,15 +277,17 @@ async function sendApiKeyElicitation(
|
|||
});
|
||||
|
||||
switch (result.action) {
|
||||
case 'accept':
|
||||
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:
|
||||
}
|
||||
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);
|
||||
|
|
@ -359,7 +361,7 @@ app.post('/api-key-form', express.urlencoded(), (req: Request, res: Response) =>
|
|||
}
|
||||
|
||||
// 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}`);
|
||||
console.log(`🔑 Received API key \u001B[32m${apiKey}\u001B[0m for session ${sessionId}`);
|
||||
|
||||
// If we have an elicitationId, complete the elicitation
|
||||
completeURLElicitation(elicitationId);
|
||||
|
|
@ -608,7 +610,7 @@ const mcpPostHandler = async (req: Request, res: Response) => {
|
|||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
code: -32_000,
|
||||
message: 'Bad Request: No valid session ID provided'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -625,7 +627,7 @@ const mcpPostHandler = async (req: Request, res: Response) => {
|
|||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
code: -32_603,
|
||||
message: 'Internal server error'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -703,6 +705,7 @@ app.delete('/mcp', authMiddleware, mcpDeleteHandler);
|
|||
app.listen(MCP_PORT, error => {
|
||||
if (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`MCP Streamable HTTP Server listening on port ${MCP_PORT}`);
|
||||
|
|
|
|||
|
|
@ -59,15 +59,15 @@ app.get('/health', c => c.json({ status: 'ok' }));
|
|||
app.all('/mcp', c => transport.handleRequest(c.req.raw));
|
||||
|
||||
// Start the server
|
||||
const PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
|
||||
const PORT = process.env.MCP_PORT ? Number.parseInt(process.env.MCP_PORT, 10) : 3000;
|
||||
|
||||
server.connect(transport).then(() => {
|
||||
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`);
|
||||
await server.connect(transport);
|
||||
|
||||
serve({
|
||||
fetch: app.fetch,
|
||||
port: PORT
|
||||
});
|
||||
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`);
|
||||
|
||||
serve({
|
||||
fetch: app.fetch,
|
||||
port: PORT
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export class InMemoryEventStore implements EventStore {
|
|||
* Generates a unique event ID for a given stream ID
|
||||
*/
|
||||
private generateEventId(streamId: string): string {
|
||||
return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
||||
return `${streamId}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,7 +54,7 @@ export class InMemoryEventStore implements EventStore {
|
|||
let foundLastEvent = false;
|
||||
|
||||
// Sort events by eventId for chronological ordering
|
||||
const sortedEvents = [...this.events.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
||||
const sortedEvents = [...this.events.entries()].toSorted((a, b) => a[0].localeCompare(b[0]));
|
||||
|
||||
for (const [eventId, { streamId: eventStreamId, message }] of sortedEvents) {
|
||||
// Only include events from the same stream
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
code: -32_000,
|
||||
message: 'Bad Request: No valid session ID provided'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -149,7 +149,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
code: -32_603,
|
||||
message: 'Internal server error'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -170,6 +170,7 @@ const PORT = 3000;
|
|||
app.listen(PORT, error => {
|
||||
if (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`MCP Streamable HTTP Server listening on port ${PORT}`);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,10 @@ async function main() {
|
|||
console.error('High-level Output Schema Example Server running on stdio');
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
code: -32_603,
|
||||
message: 'Internal server error'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -136,7 +136,7 @@ app.get('/mcp', async (req: Request, res: Response) => {
|
|||
JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
code: -32_000,
|
||||
message: 'Method not allowed.'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -150,7 +150,7 @@ app.delete('/mcp', async (req: Request, res: Response) => {
|
|||
JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
code: -32_000,
|
||||
message: 'Method not allowed.'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -163,6 +163,7 @@ const PORT = 3000;
|
|||
app.listen(PORT, error => {
|
||||
if (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
|
||||
|
|
@ -171,5 +172,6 @@ app.listen(PORT, error => {
|
|||
// Handle server shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('Shutting down server...');
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ const getServer = () => {
|
|||
};
|
||||
|
||||
switch (infoType) {
|
||||
case 'contact':
|
||||
case 'contact': {
|
||||
message = 'Please provide your contact information';
|
||||
requestedSchema = {
|
||||
type: 'object',
|
||||
|
|
@ -171,7 +171,8 @@ const getServer = () => {
|
|||
required: ['name', 'email']
|
||||
};
|
||||
break;
|
||||
case 'preferences':
|
||||
}
|
||||
case 'preferences': {
|
||||
message = 'Please set your preferences';
|
||||
requestedSchema = {
|
||||
type: 'object',
|
||||
|
|
@ -200,7 +201,8 @@ const getServer = () => {
|
|||
required: ['theme']
|
||||
};
|
||||
break;
|
||||
case 'feedback':
|
||||
}
|
||||
case 'feedback': {
|
||||
message = 'Please provide your feedback';
|
||||
requestedSchema = {
|
||||
type: 'object',
|
||||
|
|
@ -227,8 +229,10 @@ const getServer = () => {
|
|||
required: ['rating', 'recommend']
|
||||
};
|
||||
break;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown info type: ${infoType}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -515,8 +519,8 @@ const getServer = () => {
|
|||
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 MCP_PORT = process.env.MCP_PORT ? Number.parseInt(process.env.MCP_PORT, 10) : 3000;
|
||||
const AUTH_PORT = process.env.MCP_AUTH_PORT ? Number.parseInt(process.env.MCP_AUTH_PORT, 10) : 3001;
|
||||
|
||||
const app = createMcpExpressApp();
|
||||
|
||||
|
|
@ -596,7 +600,7 @@ const mcpPostHandler = async (req: Request, res: Response) => {
|
|||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
code: -32_000,
|
||||
message: 'Bad Request: No valid session ID provided'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -613,7 +617,7 @@ const mcpPostHandler = async (req: Request, res: Response) => {
|
|||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
code: -32_603,
|
||||
message: 'Internal server error'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -691,6 +695,7 @@ if (useOAuth && authMiddleware) {
|
|||
app.listen(MCP_PORT, error => {
|
||||
if (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`MCP Streamable HTTP Server listening on port ${MCP_PORT}`);
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ class TaskResultHandler {
|
|||
return {
|
||||
...result,
|
||||
_meta: {
|
||||
...(result._meta || {}),
|
||||
...result._meta,
|
||||
[RELATED_TASK_META_KEY]: { taskId }
|
||||
}
|
||||
};
|
||||
|
|
@ -452,7 +452,7 @@ class TaskSession {
|
|||
// Server Setup
|
||||
// ============================================================================
|
||||
|
||||
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 8000;
|
||||
const PORT = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 8000;
|
||||
|
||||
// Create shared stores
|
||||
const taskStore = new TaskStoreWithNotifications();
|
||||
|
|
@ -589,7 +589,7 @@ const createServer = (): Server => {
|
|||
haiku = (result.content as TextContent).text;
|
||||
}
|
||||
|
||||
console.log(`[Server] Received sampling response: ${haiku.substring(0, 50)}...`);
|
||||
console.log(`[Server] Received sampling response: ${haiku.slice(0, 50)}...`);
|
||||
console.log('[Server] Completing task with haiku');
|
||||
await taskStore.storeTaskResult(task.taskId, 'completed', {
|
||||
content: [{ type: 'text', text: `Haiku:\n${haiku}` }]
|
||||
|
|
@ -682,7 +682,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
} else {
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: { code: -32000, message: 'Bad Request: No valid session ID' },
|
||||
error: { code: -32_000, message: 'Bad Request: No valid session ID' },
|
||||
id: null
|
||||
});
|
||||
return;
|
||||
|
|
@ -694,7 +694,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
if (!res.headersSent) {
|
||||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: { code: -32603, message: 'Internal server error' },
|
||||
error: { code: -32_603, message: 'Internal server error' },
|
||||
id: null
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
code: -32_000,
|
||||
message: 'Bad Request: No valid session ID provided'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -87,7 +87,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
code: -32_603,
|
||||
message: 'Internal server error'
|
||||
},
|
||||
id: null
|
||||
|
|
@ -114,6 +114,7 @@ const PORT = 3000;
|
|||
app.listen(PORT, error => {
|
||||
if (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Server listening on port ${PORT}`);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,10 @@ async function main() {
|
|||
console.log('MCP server is running...');
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue