v2 ESlint fixes (#1403)

Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
This commit is contained in:
Konstantin Konstantinov 2026-01-20 14:21:53 +02:00 • committed by GitHub
commit 9fc7e28f48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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);
});
}