Compare commits

...

7 Commits

Author SHA1 Message Date
celestial-vault 62c267e3d6 merge conflicts 2025-04-04 12:39:54 -07:00
celestial-vault 2a64b8352d fix addLocalServerForm imports 2025-04-04 10:42:04 -07:00
celestial-vault 46647fc325 merge conflicts 2025-04-04 10:41:37 -07:00
celestial-vault fa0585a5a9 rename index files to their component names 2025-04-03 21:49:28 -07:00
celestial-vault 5408220a00 rename back because it didn't help 2025-04-03 21:20:36 -07:00
celestial-vault 88d12fd777 rename back files to make diff more readable 2025-04-03 21:07:31 -07:00
celestial-vault 179ea4516b reorganize directory structure 2025-04-03 20:22:52 -07:00
18 changed files with 250 additions and 272 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ import AccountView from "./components/account/AccountView"
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
import { FirebaseAuthProvider } from "./context/FirebaseAuthContext"
import { vscode } from "./utils/vscode"
import McpView from "./components/mcp/McpView"
import McpView from "./components/mcp/configuration/McpConfigurationView"
const AppContent = () => {
const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, vscMachineId } = useExtensionState()
@@ -1,6 +1,6 @@
import { VSCodeDataGrid, VSCodeDataGridRow, VSCodeDataGridCell } from "@vscode/webview-ui-toolkit/react"
import { useState } from "react"
import { TabButton } from "../mcp/McpView"
import { TabButton } from "../mcp/configuration/McpConfigurationView"
import { UsageTransaction, PaymentTransaction } from "../../../../src/shared/ClineAccount"
import { formatDollars, formatTimestamp } from "../../utils/format"
+3 -3
View File
@@ -23,14 +23,14 @@ import CodeAccordian, { cleanPathPrefix } from "../common/CodeAccordian"
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
import MarkdownBlock from "../common/MarkdownBlock"
import Thumbnails from "../common/Thumbnails"
import McpResourceRow from "../mcp/McpResourceRow"
import McpToolRow from "../mcp/McpToolRow"
import McpResponseDisplay from "../mcp/McpResponseDisplay"
import McpToolRow from "../mcp/configuration/tabs/installed/server-row/McpToolRow"
import McpResponseDisplay from "../mcp/chat-display/McpResponseDisplay"
import CreditLimitError from "./CreditLimitError"
import { OptionsButtons } from "./OptionsButtons"
import { highlightMentions } from "./TaskHeader"
import SuccessButton from "../common/SuccessButton"
import TaskFeedbackButtons from "./TaskFeedbackButtons"
import McpResourceRow from "../mcp/configuration/tabs/installed/server-row/McpResourceRow"
const ChatRowContainer = styled.div`
padding: 10px 6px 10px 15px;
@@ -1,8 +1,8 @@
import React, { useEffect, useRef } from "react"
import { vscode } from "../../utils/vscode"
import React from "react"
import { vscode } from "../../../utils/vscode"
import DOMPurify from "dompurify"
import { getSafeHostname, formatUrlForOpening, checkIfImageUrl } from "./McpRichUtil"
import ChatErrorBoundary from "../chat/ChatErrorBoundary"
import { getSafeHostname, formatUrlForOpening, checkIfImageUrl } from "./utils/mcpRichUtil"
import ChatErrorBoundary from "../../chat/ChatErrorBoundary"
interface ImagePreviewProps {
url: string
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from "react"
import { vscode } from "../../utils/vscode"
import React from "react"
import { vscode } from "../../../utils/vscode"
import DOMPurify from "dompurify"
import { getSafeHostname, normalizeRelativeUrl } from "./McpRichUtil"
import ChatErrorBoundary from "../chat/ChatErrorBoundary"
import { getSafeHostname, normalizeRelativeUrl } from "./utils/mcpRichUtil"
import ChatErrorBoundary from "../../chat/ChatErrorBoundary"
interface OpenGraphData {
title?: string
@@ -1,20 +1,10 @@
import React, { useEffect, useState, useCallback } from "react"
import LinkPreview from "./LinkPreview"
import ImagePreview from "./ImagePreview"
import { vscode } from "../../utils/vscode"
import DOMPurify from "dompurify"
import styled from "styled-components"
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
import ChatErrorBoundary from "../chat/ChatErrorBoundary"
import {
safeCreateUrl,
isUrl,
getSafeHostname,
isLocalhostUrl,
normalizeRelativeUrl,
formatUrlForOpening,
checkIfImageUrl,
} from "./McpRichUtil"
import { CODE_BLOCK_BG_COLOR } from "../../common/CodeBlock"
import ChatErrorBoundary from "../../chat/ChatErrorBoundary"
import { isUrl, isLocalhostUrl, formatUrlForOpening, checkIfImageUrl } from "./utils/mcpRichUtil"
// Maximum number of URLs to process in total, per response
export const MAX_URLS = 50
@@ -1,4 +1,4 @@
import { vscode } from "../../utils/vscode"
import { vscode } from "../../../../utils/vscode"
// Safely create a URL object with error handling and ensure HTTPS
export const safeCreateUrl = (url: string): URL | null => {
@@ -0,0 +1,121 @@
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { useEffect, useState } from "react"
import styled from "styled-components"
import { useExtensionState } from "../../../context/ExtensionStateContext"
import { vscode } from "../../../utils/vscode"
import AddRemoteServerForm from "./tabs/add-server/AddRemoteServerForm"
import McpMarketplaceView from "./tabs/marketplace/McpMarketplaceView"
import InstalledServersView from "./tabs/installed/InstalledServersView"
type McpViewProps = {
onDone: () => void
}
const McpConfigurationView = ({ onDone }: McpViewProps) => {
const { mcpMarketplaceEnabled } = useExtensionState()
const [activeTab, setActiveTab] = useState(mcpMarketplaceEnabled ? "marketplace" : "installed")
const handleTabChange = (tab: string) => {
setActiveTab(tab)
}
useEffect(() => {
if (!mcpMarketplaceEnabled && activeTab === "marketplace") {
// If marketplace is disabled and we're on marketplace tab, switch to installed
setActiveTab("installed")
}
}, [mcpMarketplaceEnabled, activeTab])
useEffect(() => {
if (mcpMarketplaceEnabled) {
vscode.postMessage({ type: "silentlyRefreshMcpMarketplace" })
vscode.postMessage({ type: "fetchLatestMcpServersFromHub" })
}
}, [mcpMarketplaceEnabled])
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
}}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "10px 17px 5px 20px",
}}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>MCP Servers</h3>
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
</div>
<div style={{ flex: 1, overflow: "auto" }}>
{/* Tabs container */}
<div
style={{
display: "flex",
gap: "1px",
padding: "0 20px 0 20px",
borderBottom: "1px solid var(--vscode-panel-border)",
}}>
{mcpMarketplaceEnabled && (
<TabButton isActive={activeTab === "marketplace"} onClick={() => handleTabChange("marketplace")}>
Marketplace
</TabButton>
)}
<TabButton isActive={activeTab === "addRemote"} onClick={() => handleTabChange("addRemote")}>
Remote Servers
</TabButton>
<TabButton isActive={activeTab === "installed"} onClick={() => handleTabChange("installed")}>
Installed
</TabButton>
</div>
{/* Content container */}
<div style={{ width: "100%" }}>
{mcpMarketplaceEnabled && activeTab === "marketplace" && <McpMarketplaceView />}
{activeTab === "addRemote" && <AddRemoteServerForm onServerAdded={() => handleTabChange("installed")} />}
{activeTab === "installed" && <InstalledServersView />}
</div>
</div>
</div>
)
}
const StyledTabButton = styled.button<{ isActive: boolean }>`
background: none;
border: none;
border-bottom: 2px solid ${(props) => (props.isActive ? "var(--vscode-foreground)" : "transparent")};
color: ${(props) => (props.isActive ? "var(--vscode-foreground)" : "var(--vscode-descriptionForeground)")};
padding: 8px 16px;
cursor: pointer;
font-size: 13px;
margin-bottom: -1px;
font-family: inherit;
&:hover {
color: var(--vscode-foreground);
}
`
export const TabButton = ({
children,
isActive,
onClick,
}: {
children: React.ReactNode
isActive: boolean
onClick: () => void
}) => (
<StyledTabButton isActive={isActive} onClick={onClick}>
{children}
</StyledTabButton>
)
export default McpConfigurationView
@@ -1,8 +1,7 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { vscode } from "../../../utils/vscode"
import { vscode } from "../../../../../utils/vscode"
import styled from "styled-components"
import { LINKS } from "../../../constants"
import { LINKS } from "../../../../../constants"
type AddLocalServerFormProps = {
onServerAdded: () => void
@@ -1,5 +1,5 @@
import { useCallback, useRef, useState } from "react"
import { vscode } from "../../../utils/vscode"
import { vscode } from "../../../../../utils/vscode"
import { VSCodeButton, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { useEvent } from "react-use"
import { LINKS } from "@/constants"
@@ -0,0 +1,87 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { vscode } from "../../../../../utils/vscode"
import { useExtensionState } from "../../../../../context/ExtensionStateContext"
import ServerRow from "./server-row/ServerRow"
const InstalledServersView = () => {
const { mcpServers: servers } = useExtensionState()
return (
<div style={{ padding: "16px 20px" }}>
<div
style={{
color: "var(--vscode-foreground)",
fontSize: "13px",
marginBottom: "16px",
marginTop: "5px",
}}>
The{" "}
<VSCodeLink href="https://github.com/modelcontextprotocol" style={{ display: "inline" }}>
Model Context Protocol
</VSCodeLink>{" "}
enables communication with locally running MCP servers that provide additional tools and resources to extend
Cline's capabilities. You can use{" "}
<VSCodeLink href="https://github.com/modelcontextprotocol/servers" style={{ display: "inline" }}>
community-made servers
</VSCodeLink>{" "}
or ask Cline to create new tools specific to your workflow (e.g., "add a tool that gets the latest npm docs").{" "}
<VSCodeLink href="https://x.com/sdrzn/status/1867271665086074969" style={{ display: "inline" }}>
See a demo here.
</VSCodeLink>
</div>
{servers.length > 0 ? (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
}}>
{servers.map((server) => (
<ServerRow key={server.name} server={server} />
))}
</div>
) : (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "12px",
marginTop: 20,
marginBottom: 20,
color: "var(--vscode-descriptionForeground)",
}}>
No MCP servers installed
</div>
)}
{/* Settings Section */}
<div style={{ marginBottom: "20px", marginTop: 10 }}>
<VSCodeButton
appearance="secondary"
style={{ width: "100%", marginBottom: "5px" }}
onClick={() => {
vscode.postMessage({ type: "openMcpSettings" })
}}>
<span className="codicon codicon-server" style={{ marginRight: "6px" }}></span>
Configure MCP Servers
</VSCodeButton>
<div style={{ textAlign: "center" }}>
<VSCodeLink
onClick={() => {
vscode.postMessage({
type: "openExtensionSettings",
text: "cline.mcp",
})
}}
style={{ fontSize: "12px" }}>
Advanced MCP Settings
</VSCodeLink>
</div>
</div>
</div>
)
}
export default InstalledServersView
@@ -1,4 +1,4 @@
import { McpResource, McpResourceTemplate } from "../../../../src/shared/mcp"
import { McpResource, McpResourceTemplate } from "../../../../../../../../src/shared/mcp"
type McpResourceRowProps = {
item: McpResource | McpResourceTemplate
@@ -1,7 +1,7 @@
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { McpTool } from "../../../../src/shared/mcp"
import { vscode } from "../../utils/vscode"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { McpTool } from "../../../../../../../../src/shared/mcp"
import { vscode } from "../../../../../../utils/vscode"
import { useExtensionState } from "../../../../../../context/ExtensionStateContext"
type McpToolRowProps = {
tool: McpTool
@@ -1,241 +1,22 @@
import { McpServer } from "../../../../../../../../src/shared/mcp"
import { DEFAULT_MCP_TIMEOUT_SECONDS } from "../../../../../../../../src/shared/mcp"
import { useState } from "react"
import { vscode } from "../../../../../../utils/vscode"
import {
VSCodeButton,
VSCodeLink,
VSCodeCheckbox,
VSCodeDropdown,
VSCodeOption,
VSCodePanels,
VSCodePanelTab,
VSCodePanelView,
VSCodeDropdown,
VSCodeOption,
VSCodeCheckbox,
} from "@vscode/webview-ui-toolkit/react"
import { useEffect, useState } from "react"
import styled from "styled-components"
import { DEFAULT_MCP_TIMEOUT_SECONDS, McpServer } from "../../../../src/shared/mcp"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { getMcpServerDisplayName } from "../../utils/mcp"
import { vscode } from "../../utils/vscode"
import McpMarketplaceView from "./marketplace/McpMarketplaceView"
import McpResourceRow from "./McpResourceRow"
import { getMcpServerDisplayName } from "../../../../../../utils/mcp"
import DangerButton from "../../../../../common/DangerButton"
import McpToolRow from "./McpToolRow"
import DangerButton from "../common/DangerButton"
import AddRemoteServerForm from "./tabs/AddRemoteServerForm"
import AddLocalServerForm from "./tabs/AddLocalServerForm"
import McpResourceRow from "./McpResourceRow"
import { useExtensionState } from "../../../../../../context/ExtensionStateContext"
type McpViewProps = {
onDone: () => void
}
const McpView = ({ onDone }: McpViewProps) => {
const { mcpServers: servers, mcpMarketplaceEnabled } = useExtensionState()
const [activeTab, setActiveTab] = useState(mcpMarketplaceEnabled ? "marketplace" : "installed")
const [activeSubTab, setActiveSubTab] = useState("remote")
const handleTabChange = (tab: string) => {
setActiveTab(tab)
}
useEffect(() => {
if (!mcpMarketplaceEnabled && activeTab === "marketplace") {
// If marketplace is disabled and we're on marketplace tab, switch to installed
setActiveTab("installed")
}
}, [mcpMarketplaceEnabled, activeTab])
useEffect(() => {
if (mcpMarketplaceEnabled) {
vscode.postMessage({ type: "silentlyRefreshMcpMarketplace" })
vscode.postMessage({ type: "fetchLatestMcpServersFromHub" })
}
}, [mcpMarketplaceEnabled])
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
}}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "10px 17px 5px 20px",
}}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>MCP Servers</h3>
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
</div>
<div style={{ flex: 1, overflow: "auto" }}>
{/* Tabs container */}
<div
style={{
display: "flex",
gap: "1px",
padding: "0 20px 0 20px",
borderBottom: "1px solid var(--vscode-panel-border)",
}}>
{mcpMarketplaceEnabled && (
<TabButton isActive={activeTab === "marketplace"} onClick={() => handleTabChange("marketplace")}>
Marketplace
</TabButton>
)}
<TabButton isActive={activeTab === "addRemote"} onClick={() => handleTabChange("addRemote")}>
Add Server
</TabButton>
<TabButton isActive={activeTab === "installed"} onClick={() => handleTabChange("installed")}>
Installed
</TabButton>
</div>
{/* Content container */}
<div style={{ width: "100%" }}>
{mcpMarketplaceEnabled && activeTab === "marketplace" && <McpMarketplaceView />}
{activeTab === "addRemote" && (
<div style={{ padding: "5px 0 0 0" }}>
<div
style={{
display: "flex",
gap: "1px",
padding: "0 20px 0 20px",
borderBottom: "1px solid var(--vscode-panel-border)",
}}>
<TabButton isActive={activeSubTab === "remote"} onClick={() => setActiveSubTab("remote")}>
Remote (SSE)
</TabButton>
<TabButton isActive={activeSubTab === "local"} onClick={() => setActiveSubTab("local")}>
Local (Command)
</TabButton>
</div>
<div>
{activeSubTab === "remote" && (
<AddRemoteServerForm onServerAdded={() => handleTabChange("installed")} />
)}
{activeSubTab === "local" && (
<AddLocalServerForm onServerAdded={() => handleTabChange("installed")} />
)}
</div>
</div>
)}
{activeTab === "installed" && (
<div style={{ padding: "16px 20px" }}>
<div
style={{
color: "var(--vscode-foreground)",
fontSize: "13px",
marginBottom: "16px",
marginTop: "5px",
}}>
The{" "}
<VSCodeLink href="https://github.com/modelcontextprotocol" style={{ display: "inline" }}>
Model Context Protocol
</VSCodeLink>{" "}
enables communication with locally running MCP servers that provide additional tools and resources
to extend Cline's capabilities. You can use{" "}
<VSCodeLink href="https://github.com/modelcontextprotocol/servers" style={{ display: "inline" }}>
community-made servers
</VSCodeLink>{" "}
or ask Cline to create new tools specific to your workflow (e.g., "add a tool that gets the latest
npm docs").{" "}
<VSCodeLink href="https://x.com/sdrzn/status/1867271665086074969" style={{ display: "inline" }}>
See a demo here.
</VSCodeLink>
</div>
{servers.length > 0 ? (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
}}>
{servers.map((server) => (
<ServerRow key={server.name} server={server} />
))}
</div>
) : (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "12px",
marginTop: 20,
marginBottom: 20,
color: "var(--vscode-descriptionForeground)",
}}>
No MCP servers installed
</div>
)}
{/* Settings Section */}
<div style={{ marginBottom: "20px", marginTop: 10 }}>
<VSCodeButton
appearance="secondary"
style={{ width: "100%", marginBottom: "5px" }}
onClick={() => {
vscode.postMessage({ type: "openMcpSettings" })
}}>
Edit Configuration
</VSCodeButton>
<div style={{ textAlign: "center" }}>
<VSCodeLink
onClick={() => {
vscode.postMessage({
type: "openExtensionSettings",
text: "cline.mcp",
})
}}
style={{ fontSize: "12px" }}>
Advanced MCP Settings
</VSCodeLink>
</div>
</div>
</div>
)}
</div>
</div>
</div>
)
}
const StyledTabButton = styled.button<{ isActive: boolean }>`
background: none;
border: none;
border-bottom: 2px solid ${(props) => (props.isActive ? "var(--vscode-foreground)" : "transparent")};
color: ${(props) => (props.isActive ? "var(--vscode-foreground)" : "var(--vscode-descriptionForeground)")};
padding: 8px 16px;
cursor: pointer;
font-size: 13px;
margin-bottom: -1px;
font-family: inherit;
&:hover {
color: var(--vscode-foreground);
}
`
export const TabButton = ({
children,
isActive,
onClick,
}: {
children: React.ReactNode
isActive: boolean
onClick: () => void
}) => (
<StyledTabButton isActive={isActive} onClick={onClick}>
{children}
</StyledTabButton>
)
// Server Row Component
const ServerRow = ({ server }: { server: McpServer }) => {
const { mcpMarketplaceCatalog, autoApprovalSettings } = useExtensionState()
@@ -576,4 +357,4 @@ const ServerRow = ({ server }: { server: McpServer }) => {
)
}
export default McpView
export default ServerRow
@@ -1,7 +1,7 @@
import { useCallback, useState, useRef, useMemo } from "react"
import styled from "styled-components"
import { McpMarketplaceItem, McpServer } from "../../../../../src/shared/mcp"
import { vscode } from "../../../utils/vscode"
import { McpMarketplaceItem, McpServer } from "../../../../../../../src/shared/mcp"
import { vscode } from "../../../../../utils/vscode"
import { useEvent } from "react-use"
interface McpMarketplaceCardProps {
@@ -8,9 +8,9 @@ import {
VSCodeOption,
VSCodeTextField,
} from "@vscode/webview-ui-toolkit/react"
import { McpMarketplaceItem } from "../../../../../src/shared/mcp"
import { useExtensionState } from "../../../context/ExtensionStateContext"
import { vscode } from "../../../utils/vscode"
import { McpMarketplaceItem } from "../../../../../../../src/shared/mcp"
import { useExtensionState } from "../../../../../context/ExtensionStateContext"
import { vscode } from "../../../../../utils/vscode"
import McpMarketplaceCard from "./McpMarketplaceCard"
import McpSubmitCard from "./McpSubmitCard"
const McpMarketplaceView = () => {
@@ -13,7 +13,7 @@ import { validateApiConfiguration, validateModelId } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import SettingsButton from "../common/SettingsButton"
import ApiOptions from "./ApiOptions"
import { TabButton } from "../mcp/McpView"
import { TabButton } from "../mcp/configuration/McpConfigurationView"
import { useEvent } from "react-use"
import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
const { IS_DEV } = process.env