mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-22 05:11:06 -05:00
Incorporate MCP changes (#93)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import axios from "axios"
|
||||
import fs from "fs/promises"
|
||||
import os from "os"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
@@ -10,6 +11,7 @@ import { openFile, openImage } from "../../integrations/misc/open-file"
|
||||
import { selectImages } from "../../integrations/misc/process-images"
|
||||
import { getTheme } from "../../integrations/theme/getTheme"
|
||||
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
|
||||
import { McpHub } from "../../services/mcp/McpHub"
|
||||
import { ApiProvider, ModelInfo } from "../../shared/api"
|
||||
import { findLast } from "../../shared/array"
|
||||
import { ExtensionMessage } from "../../shared/ExtensionMessage"
|
||||
@@ -70,6 +72,7 @@ export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
uiMessages: "ui_messages.json",
|
||||
openRouterModels: "openrouter_models.json",
|
||||
mcpSettings: "cline_mcp_settings.json",
|
||||
}
|
||||
|
||||
export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
@@ -80,7 +83,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
private view?: vscode.WebviewView | vscode.WebviewPanel
|
||||
private cline?: Cline
|
||||
private workspaceTracker?: WorkspaceTracker
|
||||
private latestAnnouncementId = "oct-28-2024" // update to some unique identifier when we add a new announcement
|
||||
mcpHub?: McpHub
|
||||
private latestAnnouncementId = "dec-10-2024" // update to some unique identifier when we add a new announcement
|
||||
|
||||
constructor(
|
||||
readonly context: vscode.ExtensionContext,
|
||||
@@ -89,6 +93,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
this.outputChannel.appendLine("ClineProvider instantiated")
|
||||
ClineProvider.activeInstances.add(this)
|
||||
this.workspaceTracker = new WorkspaceTracker(this)
|
||||
this.mcpHub = new McpHub(this)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -112,6 +117,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
this.workspaceTracker?.dispose()
|
||||
this.workspaceTracker = undefined
|
||||
this.mcpHub?.dispose()
|
||||
this.mcpHub = undefined
|
||||
this.outputChannel.appendLine("Disposed all disposables")
|
||||
ClineProvider.activeInstances.delete(this)
|
||||
}
|
||||
@@ -528,6 +535,21 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
.getConfiguration('roo-cline')
|
||||
.update('allowedCommands', message.commands, vscode.ConfigurationTarget.Global);
|
||||
break;
|
||||
case "openMcpSettings": {
|
||||
const mcpSettingsFilePath = await this.mcpHub?.getMcpSettingsFilePath()
|
||||
if (mcpSettingsFilePath) {
|
||||
openFile(mcpSettingsFilePath)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "restartMcpServer": {
|
||||
try {
|
||||
await this.mcpHub?.restartConnection(message.text!)
|
||||
} catch (error) {
|
||||
console.error(`Failed to retry connection for ${message.text}:`, error)
|
||||
}
|
||||
break
|
||||
}
|
||||
// Add more switch case statements here as more webview message commands
|
||||
// are created within the webview context (i.e. inside media/main.js)
|
||||
case "playSound":
|
||||
@@ -563,6 +585,24 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
|
||||
// MCP
|
||||
|
||||
async ensureMcpServersDirectoryExists(): Promise<string> {
|
||||
const mcpServersDir = path.join(os.homedir(), "Documents", "Cline", "MCP")
|
||||
try {
|
||||
await fs.mkdir(mcpServersDir, { recursive: true })
|
||||
} catch (error) {
|
||||
return "~/Documents/Cline/MCP" // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine since this path is only ever used in the system prompt
|
||||
}
|
||||
return mcpServersDir
|
||||
}
|
||||
|
||||
async ensureSettingsDirectoryExists(): Promise<string> {
|
||||
const settingsDir = path.join(this.context.globalStorageUri.fsPath, "settings")
|
||||
await fs.mkdir(settingsDir, { recursive: true })
|
||||
return settingsDir
|
||||
}
|
||||
|
||||
// Ollama
|
||||
|
||||
async getOllamaModels(baseUrl?: string) {
|
||||
|
||||
@@ -3,6 +3,53 @@ import * as vscode from 'vscode'
|
||||
import { ExtensionMessage, ExtensionState } from '../../../shared/ExtensionMessage'
|
||||
import { setSoundEnabled } from '../../../utils/sound'
|
||||
|
||||
// Mock delay module
|
||||
jest.mock('delay', () => {
|
||||
const delayFn = (ms: number) => Promise.resolve();
|
||||
delayFn.createDelay = () => delayFn;
|
||||
delayFn.reject = () => Promise.reject(new Error('Delay rejected'));
|
||||
delayFn.range = () => Promise.resolve();
|
||||
return delayFn;
|
||||
});
|
||||
|
||||
// Mock MCP-related modules
|
||||
jest.mock('@modelcontextprotocol/sdk/types.js', () => ({
|
||||
CallToolResultSchema: {},
|
||||
ListResourcesResultSchema: {},
|
||||
ListResourceTemplatesResultSchema: {},
|
||||
ListToolsResultSchema: {},
|
||||
ReadResourceResultSchema: {},
|
||||
ErrorCode: {
|
||||
InvalidRequest: 'InvalidRequest',
|
||||
MethodNotFound: 'MethodNotFound',
|
||||
InternalError: 'InternalError'
|
||||
},
|
||||
McpError: class McpError extends Error {
|
||||
code: string;
|
||||
constructor(code: string, message: string) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.name = 'McpError';
|
||||
}
|
||||
}
|
||||
}), { virtual: true });
|
||||
|
||||
jest.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
|
||||
Client: jest.fn().mockImplementation(() => ({
|
||||
connect: jest.fn().mockResolvedValue(undefined),
|
||||
close: jest.fn().mockResolvedValue(undefined),
|
||||
listTools: jest.fn().mockResolvedValue({ tools: [] }),
|
||||
callTool: jest.fn().mockResolvedValue({ content: [] })
|
||||
}))
|
||||
}), { virtual: true });
|
||||
|
||||
jest.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
|
||||
StdioClientTransport: jest.fn().mockImplementation(() => ({
|
||||
connect: jest.fn().mockResolvedValue(undefined),
|
||||
close: jest.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
}), { virtual: true });
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('vscode', () => ({
|
||||
ExtensionContext: jest.fn(),
|
||||
@@ -19,7 +66,11 @@ jest.mock('vscode', () => ({
|
||||
}),
|
||||
onDidChangeConfiguration: jest.fn().mockImplementation((callback) => ({
|
||||
dispose: jest.fn()
|
||||
}))
|
||||
})),
|
||||
onDidSaveTextDocument: jest.fn(() => ({ dispose: jest.fn() })),
|
||||
onDidChangeTextDocument: jest.fn(() => ({ dispose: jest.fn() })),
|
||||
onDidOpenTextDocument: jest.fn(() => ({ dispose: jest.fn() })),
|
||||
onDidCloseTextDocument: jest.fn(() => ({ dispose: jest.fn() }))
|
||||
},
|
||||
env: {
|
||||
uriScheme: 'vscode'
|
||||
|
||||
Reference in New Issue
Block a user