mirror of
https://github.com/cline/cline.git
synced 2026-10-29 17:42:34 +08:00
Supporting Notifications MCP with Cline (#4129)
* Adding real time client * Adding real time client * Adding real time client * first commit * Adding thinking Slider for Gemini models --------- Co-authored-by: Cline Evaluation <cline@example.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Supporting Notifications MCP with Cline
|
||||
Vendored
+38
-2
@@ -128,7 +128,25 @@
|
||||
"type": "npm",
|
||||
"script": "watch:esbuild",
|
||||
"group": "build",
|
||||
"problemMatcher": "$esbuild-watch",
|
||||
"problemMatcher": {
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^✘ \\[ERROR\\] (.*)$",
|
||||
"message": 1
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s+(.*):(\\d+):(\\d+):$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "^\\[watch\\] build started$",
|
||||
"endsPattern": "^\\[watch\\] build finished$"
|
||||
}
|
||||
},
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild",
|
||||
"dependsOn": ["npm: protos"],
|
||||
@@ -146,7 +164,25 @@
|
||||
"type": "npm",
|
||||
"script": "watch:esbuild:test",
|
||||
"group": "build",
|
||||
"problemMatcher": "$esbuild-watch",
|
||||
"problemMatcher": {
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^✘ \\[ERROR\\] (.*)$",
|
||||
"message": 1
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s+(.*):(\\d+):(\\d+):$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "^\\[watch\\] build started$",
|
||||
"endsPattern": "^\\[watch\\] build finished$"
|
||||
}
|
||||
},
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild:test",
|
||||
"dependsOn": ["npm: protos"],
|
||||
|
||||
+8
-7
@@ -65,13 +65,14 @@ enum ClineSay {
|
||||
BROWSER_ACTION_RESULT = 16;
|
||||
MCP_SERVER_REQUEST_STARTED = 17;
|
||||
MCP_SERVER_RESPONSE = 18;
|
||||
USE_MCP_SERVER_SAY = 19;
|
||||
DIFF_ERROR = 20;
|
||||
DELETED_API_REQS = 21;
|
||||
CLINEIGNORE_ERROR = 22;
|
||||
CHECKPOINT_CREATED = 23;
|
||||
LOAD_MCP_DOCUMENTATION = 24;
|
||||
INFO = 25;
|
||||
MCP_NOTIFICATION = 19;
|
||||
USE_MCP_SERVER_SAY = 20;
|
||||
DIFF_ERROR = 21;
|
||||
DELETED_API_REQS = 22;
|
||||
CLINEIGNORE_ERROR = 23;
|
||||
CHECKPOINT_CREATED = 24;
|
||||
LOAD_MCP_DOCUMENTATION = 25;
|
||||
INFO = 26;
|
||||
}
|
||||
|
||||
// Enum for ClineSayTool tool types
|
||||
|
||||
@@ -233,6 +233,12 @@ export class Task {
|
||||
this.chatSettings = chatSettings
|
||||
this.enableCheckpoints = enableCheckpointsSetting
|
||||
|
||||
// Set up MCP notification callback for real-time notifications
|
||||
this.mcpHub.setNotificationCallback(async (serverName: string, level: string, message: string) => {
|
||||
// Display notification in chat immediately
|
||||
await this.say("mcp_notification", `[${serverName}] ${message}`)
|
||||
})
|
||||
|
||||
// Initialize taskId first
|
||||
if (historyItem) {
|
||||
this.taskId = historyItem.id
|
||||
@@ -1196,6 +1202,9 @@ export class Task {
|
||||
this.clineIgnoreController.dispose()
|
||||
this.fileContextTracker.dispose()
|
||||
await this.diffViewProvider.revertChanges() // need to await for when we want to make sure directories/files are reverted before re-starting the task from a checkpoint
|
||||
|
||||
// Clear the notification callback when task is aborted
|
||||
this.mcpHub.clearNotificationCallback()
|
||||
}
|
||||
|
||||
// Checkpoints
|
||||
@@ -3323,8 +3332,21 @@ export class Task {
|
||||
|
||||
// now execute the tool
|
||||
await this.say("mcp_server_request_started") // same as browser_action_result
|
||||
|
||||
// Check for any pending notifications before the tool call
|
||||
const notificationsBefore = this.mcpHub.getPendingNotifications()
|
||||
for (const notification of notificationsBefore) {
|
||||
await this.say("mcp_notification", `[${notification.serverName}] ${notification.message}`)
|
||||
}
|
||||
|
||||
const toolResult = await this.mcpHub.callTool(server_name, tool_name, parsedArguments)
|
||||
|
||||
// Check for any pending notifications after the tool call
|
||||
const notificationsAfter = this.mcpHub.getPendingNotifications()
|
||||
for (const notification of notificationsAfter) {
|
||||
await this.say("mcp_notification", `[${notification.serverName}] ${notification.message}`)
|
||||
}
|
||||
|
||||
// TODO: add progress indicator
|
||||
|
||||
const toolResultImages =
|
||||
|
||||
@@ -54,6 +54,17 @@ export class McpHub {
|
||||
connections: McpConnection[] = []
|
||||
isConnecting: boolean = false
|
||||
|
||||
// Store notifications for display in chat
|
||||
private pendingNotifications: Array<{
|
||||
serverName: string
|
||||
level: string
|
||||
message: string
|
||||
timestamp: number
|
||||
}> = []
|
||||
|
||||
// Callback for sending notifications to active task
|
||||
private notificationCallback?: (serverName: string, level: string, message: string) => void
|
||||
|
||||
constructor(
|
||||
getMcpServersPath: () => Promise<string>,
|
||||
getSettingsDirectoryPath: () => Promise<string>,
|
||||
@@ -321,6 +332,100 @@ export class McpHub {
|
||||
connection.server.status = "connected"
|
||||
connection.server.error = ""
|
||||
|
||||
// Register notification handler for real-time messages
|
||||
console.log(`[MCP Debug] Setting up notification handlers for server: ${name}`)
|
||||
console.log(`[MCP Debug] Client instance:`, connection.client)
|
||||
console.log(`[MCP Debug] Transport type:`, config.type)
|
||||
|
||||
// Try to set notification handler using the client's method
|
||||
try {
|
||||
// Import the notification schema from MCP SDK
|
||||
const { z } = await import("zod")
|
||||
|
||||
// Define the notification schema for notifications/message
|
||||
const NotificationMessageSchema = z.object({
|
||||
method: z.literal("notifications/message"),
|
||||
params: z
|
||||
.object({
|
||||
level: z.enum(["debug", "info", "warning", "error"]).optional(),
|
||||
logger: z.string().optional(),
|
||||
data: z.string().optional(),
|
||||
message: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
// Set the notification handler
|
||||
connection.client.setNotificationHandler(NotificationMessageSchema as any, async (notification: any) => {
|
||||
console.log(`[MCP Notification] ${name}:`, JSON.stringify(notification, null, 2))
|
||||
|
||||
const params = notification.params || {}
|
||||
const level = params.level || "info"
|
||||
const data = params.data || params.message || ""
|
||||
const logger = params.logger || ""
|
||||
|
||||
console.log(`[MCP Message Notification] ${name}: level=${level}, data=${data}, logger=${logger}`)
|
||||
|
||||
// Format the message
|
||||
const message = logger ? `[${logger}] ${data}` : data
|
||||
|
||||
// Send notification directly to active task if callback is set
|
||||
if (this.notificationCallback) {
|
||||
console.log(`[MCP Debug] Sending notification to active task: ${message}`)
|
||||
this.notificationCallback(name, level, message)
|
||||
} else {
|
||||
// Fallback: store for later retrieval
|
||||
console.log(`[MCP Debug] No active task, storing notification: ${message}`)
|
||||
this.pendingNotifications.push({
|
||||
serverName: name,
|
||||
level,
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
// Also show as VS Code notification for now (can be removed later if desired)
|
||||
switch (level) {
|
||||
case "error":
|
||||
vscode.window.showErrorMessage(`MCP ${name}: ${message}`)
|
||||
break
|
||||
case "warning":
|
||||
vscode.window.showWarningMessage(`MCP ${name}: ${message}`)
|
||||
break
|
||||
default:
|
||||
vscode.window.showInformationMessage(`MCP ${name}: ${message}`)
|
||||
}
|
||||
|
||||
// Forward to webview if available
|
||||
if (this.postMessageToWebview) {
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpNotification",
|
||||
serverName: name,
|
||||
notification: {
|
||||
level,
|
||||
data,
|
||||
logger,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
})
|
||||
console.log(`[MCP Debug] Successfully set notifications/message handler for ${name}`)
|
||||
|
||||
// Also set a fallback handler for any other notification types
|
||||
connection.client.fallbackNotificationHandler = async (notification: any) => {
|
||||
console.log(`[MCP Fallback Notification] ${name}:`, JSON.stringify(notification, null, 2))
|
||||
|
||||
// Show in VS Code for visibility
|
||||
vscode.window.showInformationMessage(
|
||||
`MCP ${name}: ${notification.method || "unknown"} - ${JSON.stringify(notification.params || {})}`,
|
||||
)
|
||||
}
|
||||
console.log(`[MCP Debug] Successfully set fallback notification handler for ${name}`)
|
||||
} catch (error) {
|
||||
console.error(`[MCP Debug] Error setting notification handlers for ${name}:`, error)
|
||||
}
|
||||
|
||||
// Initial fetch of tools and resources
|
||||
connection.server.tools = await this.fetchToolsList(name)
|
||||
connection.server.resources = await this.fetchResourcesList(name)
|
||||
@@ -949,6 +1054,38 @@ export class McpHub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and clear pending notifications
|
||||
* @returns Array of pending notifications
|
||||
*/
|
||||
getPendingNotifications(): Array<{
|
||||
serverName: string
|
||||
level: string
|
||||
message: string
|
||||
timestamp: number
|
||||
}> {
|
||||
const notifications = [...this.pendingNotifications]
|
||||
this.pendingNotifications = []
|
||||
return notifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the notification callback for real-time notifications
|
||||
* @param callback Function to call when notifications arrive
|
||||
*/
|
||||
setNotificationCallback(callback: (serverName: string, level: string, message: string) => void): void {
|
||||
this.notificationCallback = callback
|
||||
console.log("[MCP Debug] Notification callback set")
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the notification callback
|
||||
*/
|
||||
clearNotificationCallback(): void {
|
||||
this.notificationCallback = undefined
|
||||
console.log("[MCP Debug] Notification callback cleared")
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
this.removeAllFileWatchers()
|
||||
for (const connection of this.connections) {
|
||||
|
||||
@@ -163,6 +163,7 @@ export type ClineSay =
|
||||
| "browser_action_result"
|
||||
| "mcp_server_request_started"
|
||||
| "mcp_server_response"
|
||||
| "mcp_notification"
|
||||
| "use_mcp_server"
|
||||
| "diff_error"
|
||||
| "deleted_api_reqs"
|
||||
|
||||
@@ -89,6 +89,7 @@ function convertClineSayToProtoEnum(say: AppClineSay | undefined): ClineSay | un
|
||||
browser_action_result: ClineSay.BROWSER_ACTION_RESULT,
|
||||
mcp_server_request_started: ClineSay.MCP_SERVER_REQUEST_STARTED,
|
||||
mcp_server_response: ClineSay.MCP_SERVER_RESPONSE,
|
||||
mcp_notification: ClineSay.MCP_NOTIFICATION,
|
||||
use_mcp_server: ClineSay.USE_MCP_SERVER_SAY,
|
||||
diff_error: ClineSay.DIFF_ERROR,
|
||||
deleted_api_reqs: ClineSay.DELETED_API_REQS,
|
||||
@@ -132,6 +133,7 @@ function convertProtoEnumToClineSay(say: ClineSay): AppClineSay | undefined {
|
||||
[ClineSay.BROWSER_ACTION_RESULT]: "browser_action_result",
|
||||
[ClineSay.MCP_SERVER_REQUEST_STARTED]: "mcp_server_request_started",
|
||||
[ClineSay.MCP_SERVER_RESPONSE]: "mcp_server_response",
|
||||
[ClineSay.MCP_NOTIFICATION]: "mcp_notification",
|
||||
[ClineSay.USE_MCP_SERVER_SAY]: "use_mcp_server",
|
||||
[ClineSay.DIFF_ERROR]: "diff_error",
|
||||
[ClineSay.DELETED_API_REQS]: "deleted_api_reqs",
|
||||
|
||||
@@ -1054,6 +1054,36 @@ export const ChatRowContent = ({
|
||||
return null // we should never see this message type
|
||||
case "mcp_server_response":
|
||||
return <McpResponseDisplay responseText={message.text || ""} />
|
||||
case "mcp_notification":
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: "8px",
|
||||
padding: "8px 12px",
|
||||
backgroundColor: "var(--vscode-textBlockQuote-background)",
|
||||
borderRadius: "4px",
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-foreground)",
|
||||
opacity: 0.9,
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-bell"
|
||||
style={{
|
||||
marginTop: "2px",
|
||||
fontSize: "14px",
|
||||
color: "var(--vscode-notificationsInfoIcon-foreground)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<div style={{ flex: 1, wordBreak: "break-word" }}>
|
||||
<span style={{ fontWeight: 500 }}>MCP Notification: </span>
|
||||
<span className="ph-no-capture">{message.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case "text":
|
||||
return (
|
||||
<WithCopyButton ref={contentRef} onMouseUp={handleMouseUp} textToCopy={message.text}>
|
||||
|
||||
Reference in New Issue
Block a user