Skip to content

Commit ddca841

Browse files
arafatkatzeCline Evaluation
andauthored
Supporting Notifications MCP with Cline (RooCodeInc#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 <[email protected]>
1 parent 099f0ec commit ddca841

File tree

8 files changed

+243
-9
lines changed

8 files changed

+243
-9
lines changed

.changeset/sour-poets-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Supporting Notifications MCP with Cline

.vscode/tasks.json

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,25 @@
128128
"type": "npm",
129129
"script": "watch:esbuild",
130130
"group": "build",
131-
"problemMatcher": "$esbuild-watch",
131+
"problemMatcher": {
132+
"pattern": [
133+
{
134+
"regexp": "^✘ \\[ERROR\\] (.*)$",
135+
"message": 1
136+
},
137+
{
138+
"regexp": "^\\s+(.*):(\\d+):(\\d+):$",
139+
"file": 1,
140+
"line": 2,
141+
"column": 3
142+
}
143+
],
144+
"background": {
145+
"activeOnStart": true,
146+
"beginsPattern": "^\\[watch\\] build started$",
147+
"endsPattern": "^\\[watch\\] build finished$"
148+
}
149+
},
132150
"isBackground": true,
133151
"label": "npm: watch:esbuild",
134152
"dependsOn": ["npm: protos"],
@@ -146,7 +164,25 @@
146164
"type": "npm",
147165
"script": "watch:esbuild:test",
148166
"group": "build",
149-
"problemMatcher": "$esbuild-watch",
167+
"problemMatcher": {
168+
"pattern": [
169+
{
170+
"regexp": "^✘ \\[ERROR\\] (.*)$",
171+
"message": 1
172+
},
173+
{
174+
"regexp": "^\\s+(.*):(\\d+):(\\d+):$",
175+
"file": 1,
176+
"line": 2,
177+
"column": 3
178+
}
179+
],
180+
"background": {
181+
"activeOnStart": true,
182+
"beginsPattern": "^\\[watch\\] build started$",
183+
"endsPattern": "^\\[watch\\] build finished$"
184+
}
185+
},
150186
"isBackground": true,
151187
"label": "npm: watch:esbuild:test",
152188
"dependsOn": ["npm: protos"],

proto/ui.proto

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ enum ClineSay {
6565
BROWSER_ACTION_RESULT = 16;
6666
MCP_SERVER_REQUEST_STARTED = 17;
6767
MCP_SERVER_RESPONSE = 18;
68-
USE_MCP_SERVER_SAY = 19;
69-
DIFF_ERROR = 20;
70-
DELETED_API_REQS = 21;
71-
CLINEIGNORE_ERROR = 22;
72-
CHECKPOINT_CREATED = 23;
73-
LOAD_MCP_DOCUMENTATION = 24;
74-
INFO = 25;
68+
MCP_NOTIFICATION = 19;
69+
USE_MCP_SERVER_SAY = 20;
70+
DIFF_ERROR = 21;
71+
DELETED_API_REQS = 22;
72+
CLINEIGNORE_ERROR = 23;
73+
CHECKPOINT_CREATED = 24;
74+
LOAD_MCP_DOCUMENTATION = 25;
75+
INFO = 26;
7576
}
7677

7778
// Enum for ClineSayTool tool types

src/core/task/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,12 @@ export class Task {
233233
this.chatSettings = chatSettings
234234
this.enableCheckpoints = enableCheckpointsSetting
235235

236+
// Set up MCP notification callback for real-time notifications
237+
this.mcpHub.setNotificationCallback(async (serverName: string, level: string, message: string) => {
238+
// Display notification in chat immediately
239+
await this.say("mcp_notification", `[${serverName}] ${message}`)
240+
})
241+
236242
// Initialize taskId first
237243
if (historyItem) {
238244
this.taskId = historyItem.id
@@ -1196,6 +1202,9 @@ export class Task {
11961202
this.clineIgnoreController.dispose()
11971203
this.fileContextTracker.dispose()
11981204
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
1205+
1206+
// Clear the notification callback when task is aborted
1207+
this.mcpHub.clearNotificationCallback()
11991208
}
12001209

12011210
// Checkpoints
@@ -3323,8 +3332,21 @@ export class Task {
33233332

33243333
// now execute the tool
33253334
await this.say("mcp_server_request_started") // same as browser_action_result
3335+
3336+
// Check for any pending notifications before the tool call
3337+
const notificationsBefore = this.mcpHub.getPendingNotifications()
3338+
for (const notification of notificationsBefore) {
3339+
await this.say("mcp_notification", `[${notification.serverName}] ${notification.message}`)
3340+
}
3341+
33263342
const toolResult = await this.mcpHub.callTool(server_name, tool_name, parsedArguments)
33273343

3344+
// Check for any pending notifications after the tool call
3345+
const notificationsAfter = this.mcpHub.getPendingNotifications()
3346+
for (const notification of notificationsAfter) {
3347+
await this.say("mcp_notification", `[${notification.serverName}] ${notification.message}`)
3348+
}
3349+
33283350
// TODO: add progress indicator
33293351

33303352
const toolResultImages =

src/services/mcp/McpHub.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ export class McpHub {
5454
connections: McpConnection[] = []
5555
isConnecting: boolean = false
5656

57+
// Store notifications for display in chat
58+
private pendingNotifications: Array<{
59+
serverName: string
60+
level: string
61+
message: string
62+
timestamp: number
63+
}> = []
64+
65+
// Callback for sending notifications to active task
66+
private notificationCallback?: (serverName: string, level: string, message: string) => void
67+
5768
constructor(
5869
getMcpServersPath: () => Promise<string>,
5970
getSettingsDirectoryPath: () => Promise<string>,
@@ -321,6 +332,100 @@ export class McpHub {
321332
connection.server.status = "connected"
322333
connection.server.error = ""
323334

335+
// Register notification handler for real-time messages
336+
console.log(`[MCP Debug] Setting up notification handlers for server: ${name}`)
337+
console.log(`[MCP Debug] Client instance:`, connection.client)
338+
console.log(`[MCP Debug] Transport type:`, config.type)
339+
340+
// Try to set notification handler using the client's method
341+
try {
342+
// Import the notification schema from MCP SDK
343+
const { z } = await import("zod")
344+
345+
// Define the notification schema for notifications/message
346+
const NotificationMessageSchema = z.object({
347+
method: z.literal("notifications/message"),
348+
params: z
349+
.object({
350+
level: z.enum(["debug", "info", "warning", "error"]).optional(),
351+
logger: z.string().optional(),
352+
data: z.string().optional(),
353+
message: z.string().optional(),
354+
})
355+
.optional(),
356+
})
357+
358+
// Set the notification handler
359+
connection.client.setNotificationHandler(NotificationMessageSchema as any, async (notification: any) => {
360+
console.log(`[MCP Notification] ${name}:`, JSON.stringify(notification, null, 2))
361+
362+
const params = notification.params || {}
363+
const level = params.level || "info"
364+
const data = params.data || params.message || ""
365+
const logger = params.logger || ""
366+
367+
console.log(`[MCP Message Notification] ${name}: level=${level}, data=${data}, logger=${logger}`)
368+
369+
// Format the message
370+
const message = logger ? `[${logger}] ${data}` : data
371+
372+
// Send notification directly to active task if callback is set
373+
if (this.notificationCallback) {
374+
console.log(`[MCP Debug] Sending notification to active task: ${message}`)
375+
this.notificationCallback(name, level, message)
376+
} else {
377+
// Fallback: store for later retrieval
378+
console.log(`[MCP Debug] No active task, storing notification: ${message}`)
379+
this.pendingNotifications.push({
380+
serverName: name,
381+
level,
382+
message,
383+
timestamp: Date.now(),
384+
})
385+
}
386+
387+
// Also show as VS Code notification for now (can be removed later if desired)
388+
switch (level) {
389+
case "error":
390+
vscode.window.showErrorMessage(`MCP ${name}: ${message}`)
391+
break
392+
case "warning":
393+
vscode.window.showWarningMessage(`MCP ${name}: ${message}`)
394+
break
395+
default:
396+
vscode.window.showInformationMessage(`MCP ${name}: ${message}`)
397+
}
398+
399+
// Forward to webview if available
400+
if (this.postMessageToWebview) {
401+
await this.postMessageToWebview({
402+
type: "mcpNotification",
403+
serverName: name,
404+
notification: {
405+
level,
406+
data,
407+
logger,
408+
timestamp: Date.now(),
409+
},
410+
} as any)
411+
}
412+
})
413+
console.log(`[MCP Debug] Successfully set notifications/message handler for ${name}`)
414+
415+
// Also set a fallback handler for any other notification types
416+
connection.client.fallbackNotificationHandler = async (notification: any) => {
417+
console.log(`[MCP Fallback Notification] ${name}:`, JSON.stringify(notification, null, 2))
418+
419+
// Show in VS Code for visibility
420+
vscode.window.showInformationMessage(
421+
`MCP ${name}: ${notification.method || "unknown"} - ${JSON.stringify(notification.params || {})}`,
422+
)
423+
}
424+
console.log(`[MCP Debug] Successfully set fallback notification handler for ${name}`)
425+
} catch (error) {
426+
console.error(`[MCP Debug] Error setting notification handlers for ${name}:`, error)
427+
}
428+
324429
// Initial fetch of tools and resources
325430
connection.server.tools = await this.fetchToolsList(name)
326431
connection.server.resources = await this.fetchResourcesList(name)
@@ -949,6 +1054,38 @@ export class McpHub {
9491054
}
9501055
}
9511056

1057+
/**
1058+
* Get and clear pending notifications
1059+
* @returns Array of pending notifications
1060+
*/
1061+
getPendingNotifications(): Array<{
1062+
serverName: string
1063+
level: string
1064+
message: string
1065+
timestamp: number
1066+
}> {
1067+
const notifications = [...this.pendingNotifications]
1068+
this.pendingNotifications = []
1069+
return notifications
1070+
}
1071+
1072+
/**
1073+
* Set the notification callback for real-time notifications
1074+
* @param callback Function to call when notifications arrive
1075+
*/
1076+
setNotificationCallback(callback: (serverName: string, level: string, message: string) => void): void {
1077+
this.notificationCallback = callback
1078+
console.log("[MCP Debug] Notification callback set")
1079+
}
1080+
1081+
/**
1082+
* Clear the notification callback
1083+
*/
1084+
clearNotificationCallback(): void {
1085+
this.notificationCallback = undefined
1086+
console.log("[MCP Debug] Notification callback cleared")
1087+
}
1088+
9521089
async dispose(): Promise<void> {
9531090
this.removeAllFileWatchers()
9541091
for (const connection of this.connections) {

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export type ClineSay =
163163
| "browser_action_result"
164164
| "mcp_server_request_started"
165165
| "mcp_server_response"
166+
| "mcp_notification"
166167
| "use_mcp_server"
167168
| "diff_error"
168169
| "deleted_api_reqs"

src/shared/proto-conversions/cline-message.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ function convertClineSayToProtoEnum(say: AppClineSay | undefined): ClineSay | un
8989
browser_action_result: ClineSay.BROWSER_ACTION_RESULT,
9090
mcp_server_request_started: ClineSay.MCP_SERVER_REQUEST_STARTED,
9191
mcp_server_response: ClineSay.MCP_SERVER_RESPONSE,
92+
mcp_notification: ClineSay.MCP_NOTIFICATION,
9293
use_mcp_server: ClineSay.USE_MCP_SERVER_SAY,
9394
diff_error: ClineSay.DIFF_ERROR,
9495
deleted_api_reqs: ClineSay.DELETED_API_REQS,
@@ -132,6 +133,7 @@ function convertProtoEnumToClineSay(say: ClineSay): AppClineSay | undefined {
132133
[ClineSay.BROWSER_ACTION_RESULT]: "browser_action_result",
133134
[ClineSay.MCP_SERVER_REQUEST_STARTED]: "mcp_server_request_started",
134135
[ClineSay.MCP_SERVER_RESPONSE]: "mcp_server_response",
136+
[ClineSay.MCP_NOTIFICATION]: "mcp_notification",
135137
[ClineSay.USE_MCP_SERVER_SAY]: "use_mcp_server",
136138
[ClineSay.DIFF_ERROR]: "diff_error",
137139
[ClineSay.DELETED_API_REQS]: "deleted_api_reqs",

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,36 @@ export const ChatRowContent = ({
10541054
return null // we should never see this message type
10551055
case "mcp_server_response":
10561056
return <McpResponseDisplay responseText={message.text || ""} />
1057+
case "mcp_notification":
1058+
return (
1059+
<div
1060+
style={{
1061+
display: "flex",
1062+
alignItems: "flex-start",
1063+
gap: "8px",
1064+
padding: "8px 12px",
1065+
backgroundColor: "var(--vscode-textBlockQuote-background)",
1066+
borderRadius: "4px",
1067+
fontSize: "13px",
1068+
color: "var(--vscode-foreground)",
1069+
opacity: 0.9,
1070+
marginBottom: "8px",
1071+
}}>
1072+
<i
1073+
className="codicon codicon-bell"
1074+
style={{
1075+
marginTop: "2px",
1076+
fontSize: "14px",
1077+
color: "var(--vscode-notificationsInfoIcon-foreground)",
1078+
flexShrink: 0,
1079+
}}
1080+
/>
1081+
<div style={{ flex: 1, wordBreak: "break-word" }}>
1082+
<span style={{ fontWeight: 500 }}>MCP Notification: </span>
1083+
<span className="ph-no-capture">{message.text}</span>
1084+
</div>
1085+
</div>
1086+
)
10571087
case "text":
10581088
return (
10591089
<WithCopyButton ref={contentRef} onMouseUp={handleMouseUp} textToCopy={message.text}>

0 commit comments

Comments
 (0)