feat(extension): complete VS Code extension with kanban board interface (#997)

---------
Co-authored-by: DavidMaliglowka <13022280+DavidMaliglowka@users.noreply.github.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
DavidMaliglowka
2025-08-01 07:04:22 -05:00
committed by GitHub
parent 60c03c548d
commit 64302dc191
101 changed files with 20608 additions and 181 deletions

View File

@@ -0,0 +1,98 @@
/**
* MCP Client Wrapper
* Handles MCP tool calls with retry logic
*/
import type { ExtensionLogger } from '../logger';
import type { MCPClientManager } from '../mcpClient';
export class MCPClient {
constructor(
private mcpClient: MCPClientManager,
private logger: ExtensionLogger,
private config: { timeout: number; retryAttempts: number }
) {}
/**
* Call MCP tool with retry logic
*/
async callTool(
toolName: string,
args: Record<string, unknown>
): Promise<any> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= this.config.retryAttempts; attempt++) {
try {
const rawResponse = await this.mcpClient.callTool(toolName, args);
this.logger.debug(
`Raw MCP response for ${toolName}:`,
JSON.stringify(rawResponse, null, 2)
);
// Parse MCP response format
if (
rawResponse &&
rawResponse.content &&
Array.isArray(rawResponse.content) &&
rawResponse.content[0]
) {
const contentItem = rawResponse.content[0];
if (contentItem.type === 'text' && contentItem.text) {
try {
const parsedData = JSON.parse(contentItem.text);
this.logger.debug(`Parsed MCP data for ${toolName}:`, parsedData);
return parsedData;
} catch (parseError) {
this.logger.error(
`Failed to parse MCP response text for ${toolName}:`,
parseError
);
this.logger.error(`Raw text was:`, contentItem.text);
return rawResponse; // Fall back to original response
}
}
}
// If not in expected format, return as-is
this.logger.warn(
`Unexpected MCP response format for ${toolName}, returning raw response`
);
return rawResponse;
} catch (error) {
lastError = error instanceof Error ? error : new Error('Unknown error');
this.logger.warn(
`Attempt ${attempt}/${this.config.retryAttempts} failed for ${toolName}:`,
lastError.message
);
if (attempt < this.config.retryAttempts) {
// Exponential backoff
const delay = Math.min(1000 * 2 ** (attempt - 1), 5000);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
throw (
lastError ||
new Error(
`Failed to call ${toolName} after ${this.config.retryAttempts} attempts`
)
);
}
/**
* Get connection status
*/
getStatus(): { isRunning: boolean; error?: string } {
return this.mcpClient.getStatus();
}
/**
* Test connection
*/
async testConnection(): Promise<boolean> {
return this.mcpClient.testConnection();
}
}