Compare commits

...

3 Commits

Author SHA1 Message Date
celestial-vault a24d48a445 remove md 2025-04-27 20:37:49 -07:00
celestial-vault 4a8ffb98e9 changeset 2025-04-27 20:26:02 -07:00
celestial-vault cf256c9f94 migrate protobus addRemoteServer 2025-04-27 20:25:36 -07:00
11 changed files with 330 additions and 83 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Migrate addRemoteServer to protobus
+14
View File
@@ -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 {
-22
View File
@@ -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
}
}
}
+2
View File
@@ -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)
}
+17 -8
View File
@@ -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)
-6
View File
@@ -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
-1
View File
@@ -9,7 +9,6 @@ import { McpViewTab } from "./mcp"
export interface WebviewMessage {
type:
| "addRemoteServer"
| "apiConfiguration"
| "webviewDidLaunch"
| "newTask"
+3 -2
View File
@@ -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",
+224
View File
@@ -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
@@ -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 (