mirror of
https://github.com/cline/cline.git
synced 2026-09-01 23:19:18 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a24d48a445 | |||
| 4a8ffb98e9 | |||
| cf256c9f94 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Migrate addRemoteServer to protobus
|
||||
@@ -7,6 +7,20 @@ import "common.proto";
|
||||
service McpService {
|
||||
rpc toggleMcpServer(ToggleMcpServerRequest) returns (McpServers);
|
||||
rpc updateMcpTimeout(UpdateMcpTimeoutRequest) returns (McpServers);
|
||||
rpc addRemoteServer(AddRemoteServerRequest) returns (AddRemoteServer);
|
||||
}
|
||||
|
||||
message AddRemoteServerRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
string server_url = 3;
|
||||
}
|
||||
|
||||
message AddRemoteServer {
|
||||
bool success = 1;
|
||||
string server_name = 2;
|
||||
optional string error = 3;
|
||||
repeated McpServer mcp_servers = 4;
|
||||
}
|
||||
|
||||
message ToggleMcpServerRequest {
|
||||
|
||||
@@ -186,28 +186,6 @@ export class Controller {
|
||||
*/
|
||||
async handleWebviewMessage(message: WebviewMessage) {
|
||||
switch (message.type) {
|
||||
case "addRemoteServer": {
|
||||
try {
|
||||
await this.mcpHub?.addRemoteServer(message.serverName!, message.serverUrl!)
|
||||
await this.postMessageToWebview({
|
||||
type: "addRemoteServerResult",
|
||||
addRemoteServerResult: {
|
||||
success: true,
|
||||
serverName: message.serverName!,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
await this.postMessageToWebview({
|
||||
type: "addRemoteServerResult",
|
||||
addRemoteServerResult: {
|
||||
success: false,
|
||||
serverName: message.serverName!,
|
||||
error: error.message,
|
||||
},
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case "authStateChanged":
|
||||
await this.setUserInfo(message.user || undefined)
|
||||
await this.postStateToWebview()
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { AddRemoteServer, AddRemoteServerRequest } from "@/shared/proto/mcp"
|
||||
import { Controller } from ".."
|
||||
import { convertMcpServersToProtoMcpServers } from "@/shared/proto-conversions/mcp/mcp-server-conversion"
|
||||
|
||||
/**
|
||||
* Add a remote MCP server
|
||||
* @param controller The controller instance
|
||||
* @param request The request message
|
||||
* @returns The result of adding the remote server
|
||||
*/
|
||||
export async function addRemoteServer(controller: Controller, request: AddRemoteServerRequest): Promise<AddRemoteServer> {
|
||||
try {
|
||||
const mcpServers = await controller.mcpHub?.addRemoteServerRPC(request.serverName, request.serverUrl)
|
||||
|
||||
const mcpServersProto = convertMcpServersToProtoMcpServers(mcpServers)
|
||||
return {
|
||||
success: true,
|
||||
serverName: request.serverName,
|
||||
mcpServers: mcpServersProto,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
serverName: request.serverName,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
mcpServers: [], // Empty array for error case
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { addRemoteServer } from "./addRemoteServer"
|
||||
import { toggleMcpServer } from "./toggleMcpServer"
|
||||
import { updateMcpTimeout } from "./updateMcpTimeout"
|
||||
|
||||
// Register all mcp service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("addRemoteServer", addRemoteServer)
|
||||
registerMethod("toggleMcpServer", toggleMcpServer)
|
||||
registerMethod("updateMcpTimeout", updateMcpTimeout)
|
||||
}
|
||||
|
||||
@@ -691,14 +691,14 @@ export class McpHub {
|
||||
}
|
||||
}
|
||||
|
||||
public async addRemoteServer(serverName: string, serverUrl: string) {
|
||||
public async addRemoteServerRPC(serverName: string, serverUrl: string) {
|
||||
try {
|
||||
const settings = await this.readAndValidateMcpSettingsFile()
|
||||
if (!settings) {
|
||||
const config = await this.readAndValidateMcpSettingsFile()
|
||||
if (!config) {
|
||||
throw new Error("Failed to read MCP settings")
|
||||
}
|
||||
|
||||
if (settings.mcpServers[serverName]) {
|
||||
if (config.mcpServers[serverName]) {
|
||||
throw new Error(`An MCP server with the name "${serverName}" already exists`)
|
||||
}
|
||||
|
||||
@@ -715,7 +715,7 @@ export class McpHub {
|
||||
|
||||
const parsedConfig = ServerConfigSchema.parse(serverConfig)
|
||||
|
||||
settings.mcpServers[serverName] = parsedConfig
|
||||
config.mcpServers[serverName] = parsedConfig
|
||||
const settingsPath = await this.getMcpSettingsFilePath()
|
||||
|
||||
// We don't write the zod-transformed version to the file.
|
||||
@@ -725,12 +725,21 @@ export class McpHub {
|
||||
// ToDo: We could benefit from input / output types reflecting the non-transformed / transformed versions
|
||||
await fs.writeFile(
|
||||
settingsPath,
|
||||
JSON.stringify({ mcpServers: { ...settings.mcpServers, [serverName]: serverConfig } }, null, 2),
|
||||
JSON.stringify({ mcpServers: { ...config.mcpServers, [serverName]: serverConfig } }, null, 2),
|
||||
)
|
||||
|
||||
await this.updateServerConnections(settings.mcpServers)
|
||||
await this.updateServerConnectionsRPC(config.mcpServers)
|
||||
|
||||
vscode.window.showInformationMessage(`Added ${serverName} MCP server`)
|
||||
const serverOrder = Object.keys(config.mcpServers || {})
|
||||
const updatedMcpServers = [...this.connections]
|
||||
.sort((a, b) => {
|
||||
const indexA = serverOrder.indexOf(a.server.name)
|
||||
const indexB = serverOrder.indexOf(b.server.name)
|
||||
return indexA - indexB
|
||||
})
|
||||
.map((connection) => connection.server)
|
||||
|
||||
return updatedMcpServers
|
||||
} catch (error) {
|
||||
console.error("Failed to add remote MCP server:", error)
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ export interface ExtensionMessage {
|
||||
| "openGraphData"
|
||||
| "isImageUrlResult"
|
||||
| "didUpdateSettings"
|
||||
| "addRemoteServerResult"
|
||||
| "userCreditsBalance"
|
||||
| "userCreditsUsage"
|
||||
| "userCreditsPayments"
|
||||
@@ -103,11 +102,6 @@ export interface ExtensionMessage {
|
||||
type: "file" | "folder"
|
||||
label?: string
|
||||
}>
|
||||
addRemoteServerResult?: {
|
||||
success: boolean
|
||||
serverName: string
|
||||
error?: string
|
||||
}
|
||||
tab?: McpViewTab
|
||||
grpc_response?: {
|
||||
message?: any // JSON serialized protobuf message
|
||||
|
||||
@@ -9,7 +9,6 @@ import { McpViewTab } from "./mcp"
|
||||
|
||||
export interface WebviewMessage {
|
||||
type:
|
||||
| "addRemoteServer"
|
||||
| "apiConfiguration"
|
||||
| "webviewDidLaunch"
|
||||
| "newTask"
|
||||
|
||||
@@ -16,8 +16,9 @@ export const AccountServiceDefinition = {
|
||||
fullName: "cline.AccountService",
|
||||
methods: {
|
||||
/**
|
||||
* The user clicked on the login link in the UI.
|
||||
* Returns the login URL.
|
||||
* Handles the user clicking the login link in the UI.
|
||||
* Generates a secure nonce for state validation, stores it in secrets,
|
||||
* and opens the authentication URL in the external browser.
|
||||
*/
|
||||
accountLoginClicked: {
|
||||
name: "accountLoginClicked",
|
||||
|
||||
@@ -53,6 +53,19 @@ export function mcpServerStatusToJSON(object: McpServerStatus): string {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AddRemoteServerRequest {
|
||||
metadata?: Metadata | undefined
|
||||
serverName: string
|
||||
serverUrl: string
|
||||
}
|
||||
|
||||
export interface AddRemoteServer {
|
||||
success: boolean
|
||||
serverName: string
|
||||
error?: string | undefined
|
||||
mcpServers: McpServer[]
|
||||
}
|
||||
|
||||
export interface ToggleMcpServerRequest {
|
||||
metadata?: Metadata | undefined
|
||||
serverName: string
|
||||
@@ -102,6 +115,209 @@ export interface McpServers {
|
||||
mcpServers: McpServer[]
|
||||
}
|
||||
|
||||
function createBaseAddRemoteServerRequest(): AddRemoteServerRequest {
|
||||
return { metadata: undefined, serverName: "", serverUrl: "" }
|
||||
}
|
||||
|
||||
export const AddRemoteServerRequest: MessageFns<AddRemoteServerRequest> = {
|
||||
encode(message: AddRemoteServerRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.metadata !== undefined) {
|
||||
Metadata.encode(message.metadata, writer.uint32(10).fork()).join()
|
||||
}
|
||||
if (message.serverName !== "") {
|
||||
writer.uint32(18).string(message.serverName)
|
||||
}
|
||||
if (message.serverUrl !== "") {
|
||||
writer.uint32(26).string(message.serverUrl)
|
||||
}
|
||||
return writer
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): AddRemoteServerRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
|
||||
let end = length === undefined ? reader.len : reader.pos + length
|
||||
const message = createBaseAddRemoteServerRequest()
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32()
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break
|
||||
}
|
||||
|
||||
message.metadata = Metadata.decode(reader, reader.uint32())
|
||||
continue
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break
|
||||
}
|
||||
|
||||
message.serverName = reader.string()
|
||||
continue
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break
|
||||
}
|
||||
|
||||
message.serverUrl = reader.string()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break
|
||||
}
|
||||
reader.skip(tag & 7)
|
||||
}
|
||||
return message
|
||||
},
|
||||
|
||||
fromJSON(object: any): AddRemoteServerRequest {
|
||||
return {
|
||||
metadata: isSet(object.metadata) ? Metadata.fromJSON(object.metadata) : undefined,
|
||||
serverName: isSet(object.serverName) ? globalThis.String(object.serverName) : "",
|
||||
serverUrl: isSet(object.serverUrl) ? globalThis.String(object.serverUrl) : "",
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: AddRemoteServerRequest): unknown {
|
||||
const obj: any = {}
|
||||
if (message.metadata !== undefined) {
|
||||
obj.metadata = Metadata.toJSON(message.metadata)
|
||||
}
|
||||
if (message.serverName !== "") {
|
||||
obj.serverName = message.serverName
|
||||
}
|
||||
if (message.serverUrl !== "") {
|
||||
obj.serverUrl = message.serverUrl
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<AddRemoteServerRequest>, I>>(base?: I): AddRemoteServerRequest {
|
||||
return AddRemoteServerRequest.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<AddRemoteServerRequest>, I>>(object: I): AddRemoteServerRequest {
|
||||
const message = createBaseAddRemoteServerRequest()
|
||||
message.metadata =
|
||||
object.metadata !== undefined && object.metadata !== null ? Metadata.fromPartial(object.metadata) : undefined
|
||||
message.serverName = object.serverName ?? ""
|
||||
message.serverUrl = object.serverUrl ?? ""
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseAddRemoteServer(): AddRemoteServer {
|
||||
return { success: false, serverName: "", error: undefined, mcpServers: [] }
|
||||
}
|
||||
|
||||
export const AddRemoteServer: MessageFns<AddRemoteServer> = {
|
||||
encode(message: AddRemoteServer, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.success !== false) {
|
||||
writer.uint32(8).bool(message.success)
|
||||
}
|
||||
if (message.serverName !== "") {
|
||||
writer.uint32(18).string(message.serverName)
|
||||
}
|
||||
if (message.error !== undefined) {
|
||||
writer.uint32(26).string(message.error)
|
||||
}
|
||||
for (const v of message.mcpServers) {
|
||||
McpServer.encode(v!, writer.uint32(34).fork()).join()
|
||||
}
|
||||
return writer
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): AddRemoteServer {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
|
||||
let end = length === undefined ? reader.len : reader.pos + length
|
||||
const message = createBaseAddRemoteServer()
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32()
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break
|
||||
}
|
||||
|
||||
message.success = reader.bool()
|
||||
continue
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break
|
||||
}
|
||||
|
||||
message.serverName = reader.string()
|
||||
continue
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break
|
||||
}
|
||||
|
||||
message.error = reader.string()
|
||||
continue
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break
|
||||
}
|
||||
|
||||
message.mcpServers.push(McpServer.decode(reader, reader.uint32()))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break
|
||||
}
|
||||
reader.skip(tag & 7)
|
||||
}
|
||||
return message
|
||||
},
|
||||
|
||||
fromJSON(object: any): AddRemoteServer {
|
||||
return {
|
||||
success: isSet(object.success) ? globalThis.Boolean(object.success) : false,
|
||||
serverName: isSet(object.serverName) ? globalThis.String(object.serverName) : "",
|
||||
error: isSet(object.error) ? globalThis.String(object.error) : undefined,
|
||||
mcpServers: globalThis.Array.isArray(object?.mcpServers)
|
||||
? object.mcpServers.map((e: any) => McpServer.fromJSON(e))
|
||||
: [],
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: AddRemoteServer): unknown {
|
||||
const obj: any = {}
|
||||
if (message.success !== false) {
|
||||
obj.success = message.success
|
||||
}
|
||||
if (message.serverName !== "") {
|
||||
obj.serverName = message.serverName
|
||||
}
|
||||
if (message.error !== undefined) {
|
||||
obj.error = message.error
|
||||
}
|
||||
if (message.mcpServers?.length) {
|
||||
obj.mcpServers = message.mcpServers.map((e) => McpServer.toJSON(e))
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<AddRemoteServer>, I>>(base?: I): AddRemoteServer {
|
||||
return AddRemoteServer.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<AddRemoteServer>, I>>(object: I): AddRemoteServer {
|
||||
const message = createBaseAddRemoteServer()
|
||||
message.success = object.success ?? false
|
||||
message.serverName = object.serverName ?? ""
|
||||
message.error = object.error ?? undefined
|
||||
message.mcpServers = object.mcpServers?.map((e) => McpServer.fromPartial(e)) || []
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseToggleMcpServerRequest(): ToggleMcpServerRequest {
|
||||
return { metadata: undefined, serverName: "", disabled: false }
|
||||
}
|
||||
@@ -897,6 +1113,14 @@ export const McpServiceDefinition = {
|
||||
responseStream: false,
|
||||
options: {},
|
||||
},
|
||||
addRemoteServer: {
|
||||
name: "addRemoteServer",
|
||||
requestType: AddRemoteServerRequest,
|
||||
requestStream: false,
|
||||
responseType: AddRemoteServer,
|
||||
responseStream: false,
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
|
||||
+36
-44
@@ -1,50 +1,20 @@
|
||||
import { useCallback, useRef, useState } from "react"
|
||||
import { useState } from "react"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { VSCodeButton, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useEvent } from "react-use"
|
||||
import { LINKS } from "@/constants"
|
||||
import { McpServiceClient } from "@/services/grpc-client"
|
||||
import { convertProtoMcpServersToMcpServers } from "@shared/proto-conversions/mcp/mcp-server-conversion"
|
||||
import { AddRemoteServer, AddRemoteServerRequest } from "@shared/proto/mcp"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
const AddRemoteServerForm = ({ onServerAdded }: { onServerAdded: () => void }) => {
|
||||
const [serverName, setServerName] = useState("")
|
||||
const [serverUrl, setServerUrl] = useState("")
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [error, setError] = useState("")
|
||||
const [showConnectingMessage, setShowConnectingMessage] = useState(false)
|
||||
const { setMcpServers } = useExtensionState()
|
||||
|
||||
// Store submitted values to check if the server was added
|
||||
const submittedValues = useRef<{ name: string } | null>(null)
|
||||
|
||||
const handleMessage = useCallback(
|
||||
(event: MessageEvent) => {
|
||||
const message = event.data
|
||||
|
||||
if (
|
||||
message.type === "addRemoteServerResult" &&
|
||||
isSubmitting &&
|
||||
submittedValues.current &&
|
||||
message.addRemoteServerResult?.serverName === submittedValues.current.name
|
||||
) {
|
||||
if (message.addRemoteServerResult.success) {
|
||||
// Handle success
|
||||
setIsSubmitting(false)
|
||||
setServerName("")
|
||||
setServerUrl("")
|
||||
submittedValues.current = null
|
||||
onServerAdded()
|
||||
setShowConnectingMessage(false)
|
||||
} else {
|
||||
// Handle error
|
||||
setIsSubmitting(false)
|
||||
setError(message.addRemoteServerResult.error || "Failed to add server")
|
||||
setShowConnectingMessage(false)
|
||||
}
|
||||
}
|
||||
},
|
||||
[isSubmitting, onServerAdded],
|
||||
)
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!serverName.trim()) {
|
||||
@@ -66,15 +36,37 @@ const AddRemoteServerForm = ({ onServerAdded }: { onServerAdded: () => void }) =
|
||||
|
||||
setError("")
|
||||
|
||||
submittedValues.current = { name: serverName.trim() }
|
||||
|
||||
setIsSubmitting(true)
|
||||
setShowConnectingMessage(true)
|
||||
vscode.postMessage({
|
||||
type: "addRemoteServer",
|
||||
serverName: serverName.trim(),
|
||||
serverUrl: serverUrl.trim(),
|
||||
})
|
||||
|
||||
try {
|
||||
const response: AddRemoteServer = await McpServiceClient.addRemoteServer({
|
||||
serverName: serverName.trim(),
|
||||
serverUrl: serverUrl.trim(),
|
||||
} as AddRemoteServerRequest)
|
||||
|
||||
if (response.success) {
|
||||
const mcpServers = convertProtoMcpServersToMcpServers(response.mcpServers)
|
||||
setMcpServers(mcpServers)
|
||||
setIsSubmitting(false)
|
||||
setServerName("")
|
||||
setServerUrl("")
|
||||
|
||||
// Switch to the installed tab
|
||||
onServerAdded()
|
||||
setShowConnectingMessage(false)
|
||||
} else {
|
||||
setIsSubmitting(false)
|
||||
setError(response.error || "Failed to add server")
|
||||
setShowConnectingMessage(false)
|
||||
}
|
||||
} catch (err) {
|
||||
// Handle any exceptions during the gRPC call
|
||||
console.error("Error adding remote server:", err)
|
||||
setIsSubmitting(false)
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
setShowConnectingMessage(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user