|
@@ -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"
|
|
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 = {
|
|
type McpViewProps = {
|
|
|
onDone: () => void
|
|
onDone: () => void
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const McpView = ({ onDone }: McpViewProps) => {
|
|
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 (
|
|
return (
|
|
|
<div
|
|
<div
|
|
@@ -126,7 +82,6 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|
|
display: "flex",
|
|
display: "flex",
|
|
|
flexDirection: "column",
|
|
flexDirection: "column",
|
|
|
}}>
|
|
}}>
|
|
|
- {/* Fixed Header */}
|
|
|
|
|
<div
|
|
<div
|
|
|
style={{
|
|
style={{
|
|
|
display: "flex",
|
|
display: "flex",
|
|
@@ -138,11 +93,10 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|
|
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
|
|
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {/* Scrollable Content */}
|
|
|
|
|
<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
|
|
<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
|
|
|
<p style={{ color: "var(--vscode-foreground)", fontSize: "13px" }}>
|
|
<p style={{ color: "var(--vscode-foreground)", fontSize: "13px" }}>
|
|
|
MCP (Model Context Protocol) enables AI models to access external tools and data through
|
|
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.
|
|
and real-time data access.
|
|
|
</p>
|
|
</p>
|
|
|
|
|
|
|
@@ -151,51 +105,22 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|
|
{servers.map((server) => (
|
|
{servers.map((server) => (
|
|
|
<ServerRow key={server.name} server={server} />
|
|
<ServerRow key={server.name} server={server} />
|
|
|
))}
|
|
))}
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- {/* 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>
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ {/* 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>
|
|
</div>
|
|
|
|
|
|
|
|
- {/* Add bottom padding for scrolling */}
|
|
|
|
|
|
|
+ {/* Bottom padding */}
|
|
|
<div style={{ height: "20px" }} />
|
|
<div style={{ height: "20px" }} />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -205,8 +130,6 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|
|
// Server Row Component
|
|
// Server Row Component
|
|
|
const ServerRow = ({ server }: { server: McpServer }) => {
|
|
const ServerRow = ({ server }: { server: McpServer }) => {
|
|
|
const [isExpanded, setIsExpanded] = useState(false)
|
|
const [isExpanded, setIsExpanded] = useState(false)
|
|
|
- const [isEditing, setIsEditing] = useState(false)
|
|
|
|
|
- const [editConfig, setEditConfig] = useState(server.config)
|
|
|
|
|
|
|
|
|
|
const getStatusColor = () => {
|
|
const getStatusColor = () => {
|
|
|
switch (server.status) {
|
|
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 = () => {
|
|
const handleRowClick = () => {
|
|
|
if (!server.error) {
|
|
if (!server.error) {
|
|
|
setIsExpanded(!isExpanded)
|
|
setIsExpanded(!isExpanded)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ const handleRetry = () => {
|
|
|
|
|
+ vscode.postMessage({
|
|
|
|
|
+ type: "retryMcpServer",
|
|
|
|
|
+ text: server.name,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return (
|
|
return (
|
|
|
<div style={{ marginBottom: "10px" }}>
|
|
<div style={{ marginBottom: "10px" }}>
|
|
|
<div
|
|
<div
|
|
@@ -271,18 +190,21 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
|
|
style={{
|
|
style={{
|
|
|
padding: "8px",
|
|
padding: "8px",
|
|
|
fontSize: "13px",
|
|
fontSize: "13px",
|
|
|
- color: "var(--vscode-testing-iconFailed)",
|
|
|
|
|
background: "var(--vscode-list-hoverBackground)",
|
|
background: "var(--vscode-list-hoverBackground)",
|
|
|
borderRadius: "0 0 4px 4px",
|
|
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>
|
|
</div>
|
|
|
) : (
|
|
) : (
|
|
|
isExpanded && (
|
|
isExpanded && (
|
|
|
<div
|
|
<div
|
|
|
style={{
|
|
style={{
|
|
|
background: "var(--vscode-list-hoverBackground)",
|
|
background: "var(--vscode-list-hoverBackground)",
|
|
|
- padding: "0 12px 12px 12px",
|
|
|
|
|
|
|
+ padding: "0 12px 0 12px",
|
|
|
fontSize: "13px",
|
|
fontSize: "13px",
|
|
|
borderRadius: "0 0 4px 4px",
|
|
borderRadius: "0 0 4px 4px",
|
|
|
}}>
|
|
}}>
|
|
@@ -361,47 +283,6 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
|
|
)}
|
|
)}
|
|
|
</VSCodePanelView>
|
|
</VSCodePanelView>
|
|
|
</VSCodePanels>
|
|
</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>
|
|
</div>
|
|
|
)
|
|
)
|
|
|
)}
|
|
)}
|