mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 12:51:17 -05:00
Add McpHub and sync with McpView
This commit is contained in:
@@ -1,119 +1,75 @@
|
||||
import {
|
||||
VSCodeButton,
|
||||
VSCodeDivider,
|
||||
VSCodeTextArea,
|
||||
VSCodeTextField,
|
||||
VSCodeTag,
|
||||
VSCodePanelTab,
|
||||
VSCodePanelView,
|
||||
VSCodeDataGrid,
|
||||
VSCodeDataGridRow,
|
||||
VSCodeDataGridCell,
|
||||
VSCodePanels,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeButton, VSCodePanels, VSCodePanelTab, VSCodePanelView } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useState } from "react"
|
||||
|
||||
type McpServer = {
|
||||
name: string
|
||||
config: string // JSON config
|
||||
status: "connected" | "connecting" | "disconnected"
|
||||
error?: string
|
||||
tools?: any[] // We'll type this properly later
|
||||
resources?: any[] // We'll type this properly later
|
||||
}
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { McpServer } from "../../../../src/shared/mcp"
|
||||
|
||||
type McpViewProps = {
|
||||
onDone: () => void
|
||||
}
|
||||
|
||||
const McpView = ({ onDone }: McpViewProps) => {
|
||||
const [isAdding, setIsAdding] = useState(false)
|
||||
const [servers, setServers] = useState<McpServer[]>([
|
||||
// Add some mock servers for testing
|
||||
{
|
||||
name: "local-tools",
|
||||
config: JSON.stringify({
|
||||
mcpServers: {
|
||||
"local-tools": {
|
||||
command: "npx",
|
||||
args: ["-y", "@modelcontextprotocol/server-tools"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
status: "connected",
|
||||
tools: [
|
||||
{
|
||||
name: "execute_command",
|
||||
description: "Run a shell command on the local system",
|
||||
},
|
||||
{
|
||||
name: "read_file",
|
||||
description: "Read contents of a file from the filesystem",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "postgres-db",
|
||||
config: JSON.stringify({
|
||||
mcpServers: {
|
||||
"postgres-db": {
|
||||
command: "npx",
|
||||
args: ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
status: "disconnected",
|
||||
error: "Failed to connect to database: Connection refused",
|
||||
},
|
||||
{
|
||||
name: "github-tools",
|
||||
config: JSON.stringify({
|
||||
mcpServers: {
|
||||
"github-tools": {
|
||||
command: "npx",
|
||||
args: ["-y", "@modelcontextprotocol/server-github"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
status: "connecting",
|
||||
resources: [
|
||||
{
|
||||
uri: "github://repo/issues",
|
||||
name: "Repository Issues",
|
||||
},
|
||||
{
|
||||
uri: "github://repo/pulls",
|
||||
name: "Pull Requests",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
const [configInput, setConfigInput] = useState("")
|
||||
|
||||
const handleAddServer = () => {
|
||||
try {
|
||||
const config = JSON.parse(configInput)
|
||||
const serverName = Object.keys(config.mcpServers)[0]
|
||||
|
||||
setServers((prev) => [
|
||||
...prev,
|
||||
{
|
||||
name: serverName,
|
||||
config: configInput,
|
||||
status: "connecting",
|
||||
},
|
||||
])
|
||||
|
||||
setIsAdding(false)
|
||||
setConfigInput("")
|
||||
|
||||
// Here you would trigger the actual server connection
|
||||
// and update its status/tools/resources accordingly
|
||||
} catch (e) {
|
||||
// Handle invalid JSON
|
||||
console.error("Invalid server configuration:", e)
|
||||
}
|
||||
}
|
||||
const { mcpServers: servers } = useExtensionState()
|
||||
// const [servers, setServers] = useState<McpServer[]>([
|
||||
// // Add some mock servers for testing
|
||||
// {
|
||||
// name: "local-tools",
|
||||
// config: JSON.stringify({
|
||||
// mcpServers: {
|
||||
// "local-tools": {
|
||||
// command: "npx",
|
||||
// args: ["-y", "@modelcontextprotocol/server-tools"],
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// status: "connected",
|
||||
// tools: [
|
||||
// {
|
||||
// name: "execute_command",
|
||||
// description: "Run a shell command on the local system",
|
||||
// },
|
||||
// {
|
||||
// name: "read_file",
|
||||
// description: "Read contents of a file from the filesystem",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// name: "postgres-db",
|
||||
// config: JSON.stringify({
|
||||
// mcpServers: {
|
||||
// "postgres-db": {
|
||||
// command: "npx",
|
||||
// args: ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// status: "disconnected",
|
||||
// error: "Failed to connect to database: Connection refused",
|
||||
// },
|
||||
// {
|
||||
// name: "github-tools",
|
||||
// config: JSON.stringify({
|
||||
// mcpServers: {
|
||||
// "github-tools": {
|
||||
// command: "npx",
|
||||
// args: ["-y", "@modelcontextprotocol/server-github"],
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// status: "connecting",
|
||||
// resources: [
|
||||
// {
|
||||
// uri: "github://repo/issues",
|
||||
// name: "Repository Issues",
|
||||
// },
|
||||
// {
|
||||
// uri: "github://repo/pulls",
|
||||
// name: "Pull Requests",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ])
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -126,7 +82,6 @@ const McpView = ({ onDone }: McpViewProps) => {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
{/* Fixed Header */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -138,11 +93,10 @@ const McpView = ({ onDone }: McpViewProps) => {
|
||||
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
|
||||
</div>
|
||||
|
||||
{/* Scrollable Content */}
|
||||
<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
|
||||
<p style={{ color: "var(--vscode-foreground)", fontSize: "13px" }}>
|
||||
MCP (Model Context Protocol) enables AI models to access external tools and data through
|
||||
standardized interfaces. Add MCP servers to extend Claude's capabilities with custom functionality
|
||||
standardized interfaces. These MCP servers extend Claude's capabilities with custom functionality
|
||||
and real-time data access.
|
||||
</p>
|
||||
|
||||
@@ -151,51 +105,22 @@ const McpView = ({ onDone }: McpViewProps) => {
|
||||
{servers.map((server) => (
|
||||
<ServerRow key={server.name} server={server} />
|
||||
))}
|
||||
|
||||
{/* Add Server UI as a row */}
|
||||
{isAdding ? (
|
||||
<div
|
||||
style={{
|
||||
padding: "12px",
|
||||
background: "var(--vscode-list-hoverBackground)",
|
||||
borderRadius: "4px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
}}>
|
||||
<b style={{ color: "var(--vscode-foreground)" }}>New MCP Server</b>
|
||||
<p style={{ color: "var(--vscode-descriptionForeground)", fontSize: "13px", margin: "0" }}>
|
||||
Enter the MCP server configuration in JSON format. You can find this configuration in
|
||||
the setup instructions for your MCP server. The config defines how to start and connect
|
||||
to the server, typically specifying a command and arguments.
|
||||
</p>
|
||||
<VSCodeTextArea
|
||||
rows={4}
|
||||
placeholder='{"mcpServers": {"server-name": {"command": "...", "args": [...]}}}'
|
||||
value={configInput}
|
||||
onChange={(e) => setConfigInput((e.target as HTMLTextAreaElement).value)}
|
||||
/>
|
||||
<div style={{ display: "flex", gap: "10px" }}>
|
||||
<VSCodeButton style={{ flex: 1 }} onClick={handleAddServer}>
|
||||
Save
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
style={{ flex: 1 }}
|
||||
appearance="secondary"
|
||||
onClick={() => setIsAdding(false)}>
|
||||
Cancel
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<VSCodeButton style={{ width: "100%" }} onClick={() => setIsAdding(true)}>
|
||||
<span className="codicon codicon-add" style={{ marginRight: "6px" }}></span>
|
||||
Add MCP Server
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Add bottom padding for scrolling */}
|
||||
{/* Edit Settings Button */}
|
||||
<div style={{ marginTop: "10px", width: "100%" }}>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
style={{ width: "100%" }}
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "openMcpSettings" })
|
||||
}}>
|
||||
<span className="codicon codicon-edit" style={{ marginRight: "6px" }}></span>
|
||||
Edit MCP Settings
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
{/* Bottom padding */}
|
||||
<div style={{ height: "20px" }} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -205,8 +130,6 @@ const McpView = ({ onDone }: McpViewProps) => {
|
||||
// Server Row Component
|
||||
const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [editConfig, setEditConfig] = useState(server.config)
|
||||
|
||||
const getStatusColor = () => {
|
||||
switch (server.status) {
|
||||
@@ -219,23 +142,19 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveConfig = () => {
|
||||
try {
|
||||
JSON.parse(editConfig) // Validate JSON
|
||||
// Here you would update the server config
|
||||
setIsEditing(false)
|
||||
} catch (e) {
|
||||
console.error("Invalid JSON config:", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't allow expansion if server has error
|
||||
const handleRowClick = () => {
|
||||
if (!server.error) {
|
||||
setIsExpanded(!isExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRetry = () => {
|
||||
vscode.postMessage({
|
||||
type: "retryMcpServer",
|
||||
text: server.name,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
<div
|
||||
@@ -271,18 +190,21 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
style={{
|
||||
padding: "8px",
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-testing-iconFailed)",
|
||||
background: "var(--vscode-list-hoverBackground)",
|
||||
borderRadius: "0 0 4px 4px",
|
||||
}}>
|
||||
{server.error}
|
||||
<div style={{ color: "var(--vscode-testing-iconFailed)", marginBottom: "8px" }}>{server.error}</div>
|
||||
<VSCodeButton appearance="secondary" onClick={handleRetry}>
|
||||
<span className="codicon codicon-debug-restart" style={{ marginRight: "6px" }}></span>
|
||||
Retry Connection
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
) : (
|
||||
isExpanded && (
|
||||
<div
|
||||
style={{
|
||||
background: "var(--vscode-list-hoverBackground)",
|
||||
padding: "0 12px 12px 12px",
|
||||
padding: "0 12px 0 12px",
|
||||
fontSize: "13px",
|
||||
borderRadius: "0 0 4px 4px",
|
||||
}}>
|
||||
@@ -361,47 +283,6 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
)}
|
||||
</VSCodePanelView>
|
||||
</VSCodePanels>
|
||||
|
||||
{/* Edit/Remove Buttons */}
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px", marginTop: "0px" }}>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<VSCodeTextArea
|
||||
rows={4}
|
||||
value={editConfig}
|
||||
onChange={(e) => setEditConfig((e.target as HTMLTextAreaElement).value)}
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<VSCodeButton onClick={handleSaveConfig} style={{ flex: 1 }}>
|
||||
Save
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={() => setIsEditing(false)}
|
||||
style={{ flex: 1 }}>
|
||||
Cancel
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={() => setIsEditing(true)}
|
||||
style={{ flex: 1 }}>
|
||||
Edit
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
style={{
|
||||
flex: 1,
|
||||
}}>
|
||||
Remove
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -10,12 +10,14 @@ import {
|
||||
import { vscode } from "../utils/vscode"
|
||||
import { convertTextMateToHljs } from "../utils/textMateToHljs"
|
||||
import { findLastIndex } from "../../../src/shared/array"
|
||||
import { McpServer } from "../../../src/shared/mcp"
|
||||
|
||||
interface ExtensionStateContextType extends ExtensionState {
|
||||
didHydrateState: boolean
|
||||
showWelcome: boolean
|
||||
theme: any
|
||||
openRouterModels: Record<string, ModelInfo>
|
||||
mcpServers: McpServer[]
|
||||
filePaths: string[]
|
||||
setApiConfiguration: (config: ApiConfiguration) => void
|
||||
setCustomInstructions: (value?: string) => void
|
||||
@@ -39,6 +41,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
|
||||
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
|
||||
})
|
||||
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
|
||||
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
@@ -95,6 +98,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
})
|
||||
break
|
||||
}
|
||||
case "mcpServers": {
|
||||
setMcpServers(message.mcpServers ?? [])
|
||||
break
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -110,6 +117,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
showWelcome,
|
||||
theme,
|
||||
openRouterModels,
|
||||
mcpServers,
|
||||
filePaths,
|
||||
setApiConfiguration: (value) => setState((prevState) => ({ ...prevState, apiConfiguration: value })),
|
||||
setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })),
|
||||
|
||||
Reference in New Issue
Block a user