Skip to content

Commit 8291715

Browse files
committed
Improve MCP tool call progress
1 parent ec409a2 commit 8291715

File tree

5 files changed

+161
-73
lines changed

5 files changed

+161
-73
lines changed

docs/protocol.md

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,9 @@ type ChatContent =
396396
| URLContent
397397
| ProgressContent
398398
| FileChangeContent
399-
| MCPToolCallContent;
399+
| MCPToolCallPrepareContent
400+
| MCPToolCallRunContent
401+
| MCPToolCalledContent;
400402

401403
/**
402404
* Simple text message from the LLM
@@ -407,19 +409,6 @@ interface TextContent {
407409
* The text content
408410
*/
409411
text: string;
410-
/**
411-
* Optional code blocks found in the text
412-
*/
413-
codeBlocks?: [{
414-
/**
415-
* The code content
416-
*/
417-
code: string;
418-
/**
419-
* The programming language of the code
420-
*/
421-
language?: string;
422-
}];
423412
}
424413

425414
/**
@@ -508,10 +497,37 @@ interface FileChangeContent {
508497
}
509498

510499
/**
511-
* MCP tool calls that LLM may trigger.
500+
* MCP tool call that LLM is preparing to execute.
501+
*/
502+
interface MCPToolCallPrepareContent {
503+
type: 'mcpToolCallPrepare';
504+
505+
/**
506+
* id of the tool call
507+
*/
508+
id: string;
509+
510+
/**
511+
* Name of the tool
512+
*/
513+
name: string;
514+
515+
/*
516+
* Argument text of this tool call
517+
*/
518+
argumentText: string;
519+
520+
/**
521+
* Whether this call requires manual approval from the user.
522+
*/
523+
manualApproval: boolean;
524+
}
525+
526+
/**
527+
* MCP tool call final request that LLM may trigger.
512528
*/
513-
interface MCPToolCallContent {
514-
type: 'mcpToolCall';
529+
interface MCPToolCallRunContent {
530+
type: 'mcpToolCallRun';
515531

516532
/**
517533
* id of the tool call
@@ -533,6 +549,48 @@ interface MCPToolCallContent {
533549
*/
534550
manualApproval: boolean;
535551
}
552+
553+
/**
554+
* MCP tool call result that LLM trigerred and was executed already.
555+
*/
556+
interface MCPToolCalledContent {
557+
type: 'mcpToolCalled';
558+
559+
/**
560+
* id of the tool call
561+
*/
562+
id: string;
563+
564+
/**
565+
* Name of the tool
566+
*/
567+
name: string;
568+
569+
/*
570+
* Arguments of this tool call
571+
*/
572+
arguments: string[];
573+
574+
/**
575+
* the result of the MCP tool call.
576+
*/
577+
outputs: [{
578+
/*
579+
* The type of this output
580+
*/
581+
type: 'text';
582+
583+
/**
584+
* The content of this output
585+
*/
586+
content: string;
587+
588+
/**
589+
* Whether it was a error
590+
*/
591+
error: boolean;
592+
}];
593+
}
536594
```
537595

538596
### Chat Query Context (↩️)

src/eca/db.clj

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
(ns eca.db
2-
"Namespace for database state management in the Editor Code Assistant (ECA).
3-
4-
Defines `initial-db` as the default application state map and `db*` as the atom
5-
that holds the mutable runtime state.")
1+
(ns eca.db)
62

73
(set! *warn-on-reflection* true)
84

src/eca/features/chat.clj

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,24 @@
167167
:role :system
168168
:content {:type :progress
169169
:state :finished}}))))
170+
:on-prepare-tool-call (fn [{:keys [id name argument]}]
171+
(messenger/chat-content-received
172+
messenger
173+
{:chat-id chat-id
174+
:request-id request-id
175+
:role :assistant
176+
:content {:type :mcpToolCallPrepare
177+
:name name
178+
:argumentText argument
179+
:id id
180+
:manual-approval false}}))
170181
:on-tool-called (fn [{:keys [id name arguments]}]
171182
(messenger/chat-content-received
172183
messenger
173184
{:chat-id chat-id
174185
:request-id request-id
175186
:role :assistant
176-
:content {:type :mcpToolCall
187+
:content {:type :mcpToolCallRun
177188
:name name
178189
:arguments arguments
179190
:id id
@@ -188,7 +199,7 @@
188199
:name name
189200
:arguments arguments
190201
:id id
191-
:output (:contents result)}})
202+
:outputs (:contents result)}})
192203
result))
193204
:on-reason (fn [{:keys [status]}]
194205
(let [msg (case status

src/eca/llm_api.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
(defn complete!
2424
[{:keys [model model-config context user-prompt config on-first-message-received
25-
on-message-received on-error on-tool-called on-reason
25+
on-message-received on-error on-prepare-tool-call on-tool-called on-reason
2626
past-messages mcp-tools]}]
2727
(let [first-message-received* (atom false)
2828
on-message-received-wrapper (fn [& args]
@@ -47,6 +47,7 @@
4747
:api-key (:openaiApiKey config)}
4848
{:on-message-received on-message-received-wrapper
4949
:on-error on-error
50+
:on-prepare-tool-call on-prepare-tool-call
5051
:on-tool-called on-tool-called
5152
:on-reason on-reason})
5253

src/eca/llm_providers/openai.clj

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252

5353
(defn completion! [{:keys [model user-prompt context temperature api-key past-messages tools web-search]
5454
:or {temperature 1.0}}
55-
{:keys [on-message-received on-error on-tool-called on-reason]}]
55+
{:keys [on-message-received on-error on-prepare-tool-call on-tool-called on-reason]}]
5656
(let [input (conj past-messages {:role "user" :content user-prompt})
5757
tools (cond-> tools
5858
web-search (conj {:type "web_search_preview"}))
@@ -63,54 +63,76 @@
6363
:temperature temperature
6464
:tools tools
6565
:stream true}
66-
on-response-fn (fn handle-response [event data]
67-
(llm-util/log-response logger-tag event data)
68-
(case event
69-
;; text
70-
"response.output_text.delta" (on-message-received {:type :text
71-
:text (:delta data)})
72-
;; tools
73-
"response.output_item.done" (case (:type (:item data))
74-
"function_call" (let [function-name (-> data :item :name)
75-
function-args (-> data :item :arguments)
76-
response (on-tool-called {:id (-> data :item :call_id)
77-
:name function-name
78-
:arguments (json/parse-string function-args)})]
79-
(base-completion-request!
80-
{:body (assoc body :input (concat input
81-
[{:type "function_call"
82-
:call_id (-> data :item :call_id)
83-
:name function-name
84-
:arguments function-args}]
85-
(mapv
86-
(fn [{:keys [_type content]}]
87-
;; TODO handle different types
88-
{:type "function_call_output"
89-
:call_id (-> data :item :call_id)
90-
:output content})
91-
(:contents response))))
92-
:api-key api-key
93-
:on-error on-error
94-
:on-response handle-response}))
95-
"reasoning" (on-reason {:status :finished})
96-
nil)
97-
;; URL mentioned
98-
"response.output_text.annotation.added" (case (-> data :annotation :type)
99-
"url_citation" (on-message-received
100-
{:type :url
101-
:title (-> data :annotation :title)
102-
:url (-> data :annotation :url)})
103-
nil)
104-
;; reasoning
105-
"response.output_item.added" (case (-> data :item :type)
106-
"reasoning" (on-reason {:status :started})
107-
nil)
66+
mcp-call-by-item-id* (atom {})
67+
on-response-fn
68+
(fn handle-response [event data]
69+
(llm-util/log-response logger-tag event data)
70+
(case event
71+
;; text
72+
"response.output_text.delta"
73+
(on-message-received {:type :text
74+
:text (:delta data)})
75+
;; tools
76+
"response.function_call_arguments.delta" (let [call (get @mcp-call-by-item-id* (:item_id data))]
77+
(on-prepare-tool-call {:id (:id call)
78+
:name (:name call)
79+
:argumentsText (:delta data)}))
10880

109-
;; done
110-
"response.completed" (when-not (= "function_call" (-> data :response :output last :type))
111-
(on-message-received {:type :finish
112-
:finish-reason (-> data :response :status)}))
113-
nil))]
81+
"response.output_item.done"
82+
(case (:type (:item data))
83+
"function_call" (let [function-name (-> data :item :name)
84+
function-args (-> data :item :arguments)
85+
response (on-tool-called {:id (-> data :item :call_id)
86+
:name function-name
87+
:arguments (json/parse-string function-args)})]
88+
(base-completion-request!
89+
{:body (assoc body :input (concat input
90+
[{:type "function_call"
91+
:call_id (-> data :item :call_id)
92+
:name function-name
93+
:arguments function-args}]
94+
(mapv
95+
(fn [{:keys [_type content]}]
96+
;; TODO handle different types
97+
{:type "function_call_output"
98+
:call_id (-> data :item :call_id)
99+
:output content})
100+
(:contents response))))
101+
:api-key api-key
102+
:on-error on-error
103+
:on-response handle-response})
104+
(swap! mcp-call-by-item-id* dissoc (-> data :item :id)))
105+
"reasoning" (on-reason {:status :finished})
106+
nil)
107+
108+
;; URL mentioned
109+
"response.output_text.annotation.added"
110+
(case (-> data :annotation :type)
111+
"url_citation" (on-message-received
112+
{:type :url
113+
:title (-> data :annotation :title)
114+
:url (-> data :annotation :url)})
115+
nil)
116+
117+
;; reasoning / tools
118+
"response.output_item.added"
119+
(case (-> data :item :type)
120+
"reasoning" (on-reason {:status :started})
121+
"function_call" (let [call-id (-> data :item :call_id)
122+
item-id (-> data :item :id)
123+
name (-> data :item :name)]
124+
(swap! mcp-call-by-item-id* assoc item-id {:name name :id call-id})
125+
(on-prepare-tool-call {:id (-> data :item :call_id)
126+
:name (-> data :item :name)
127+
:argumentsText (-> data :item :arguments)}))
128+
nil)
129+
130+
;; done
131+
"response.completed"
132+
(when-not (= "function_call" (-> data :response :output last :type))
133+
(on-message-received {:type :finish
134+
:finish-reason (-> data :response :status)}))
135+
nil))]
114136
(base-completion-request!
115137
{:body body
116138
:api-key api-key

0 commit comments

Comments
 (0)