Skip to content

Commit 0fd1927

Browse files
authored
Merge branch 'master' into logback-mdc-logger
2 parents cc15887 + 4acb25f commit 0fd1927

12 files changed

Lines changed: 317 additions & 30 deletions

File tree

CHANGELOG.md

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

33
## Unreleased
44

5-
- Use a JSON-RPC `ping` (instead of `initialize`) for the OAuth auth-discovery probe, so the probe POST is never counted as a real handshake by servers or tests that track requests by method name.
65
- Replace custom stderr-print logger with Logback/SLF4J: timestamps, log levels, chat-id MDC context, third-party noise suppression (root at WARN, `eca` at INFO), and proper cross-thread context propagation in `future*`. #253
6+
7+
## 0.134.2
8+
9+
- Bugfix: OpenAI Responses tool calls now opt out of strict schema normalization so optional tool parameters remain optional.
10+
- Bugfix: MCP tool calls now route to the selected server when multiple servers expose the same tool name.
11+
- Use a JSON-RPC `ping` (instead of `initialize`) for the OAuth auth-discovery probe, so the probe POST is never counted as a real handshake by servers or tests that track requests by method name.
12+
- Guard subagent activity labels against overly long model-generated text.
13+
714
## 0.134.1
815

916
- Support optional `clientName` config field for MCP servers to override the OAuth Dynamic Client Registration `client_name` (useful for servers that allowlist clients by name, e.g. Figma).

docs/protocol.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2667,6 +2667,25 @@ interface ServerResource {
26672667
}
26682668
```
26692669

2670+
### Tool server removed (⬅️)
2671+
2672+
A server notification that an MCP server has been removed (via `mcp/removeServer`).
2673+
Clients should drop the corresponding entry from any cached server list / UI.
2674+
2675+
_Notification:_
2676+
2677+
* method: `tool/serverRemoved`
2678+
* params: `ToolServerRemovedParams` defined as follows:
2679+
2680+
```typescript
2681+
interface ToolServerRemovedParams {
2682+
/**
2683+
* The MCP server name that was removed.
2684+
*/
2685+
name: string;
2686+
}
2687+
```
2688+
26702689
### Stop MCP server (➡️)
26712690

26722691
A client notification for server to stop a MCP server, stopping the process.
@@ -2817,6 +2836,158 @@ _Response:_
28172836

28182837
* result: `{}`
28192838

2839+
### Add MCP Server (↩️)
2840+
2841+
Adds a new MCP server definition at runtime, persists it to the chosen config file
2842+
(global or workspace-local), and starts the server asynchronously. On success the server
2843+
also emits a follow-up `tool/serverUpdated` notification as the new server progresses
2844+
through `starting``running` / `failed` / `requires-auth`. Clients should not
2845+
optimistically add the row locally — they should rely on that broadcast.
2846+
2847+
_Request:_
2848+
2849+
* method: `mcp/addServer`
2850+
* params: `MCPAddServerParams` defined as follows:
2851+
2852+
```typescript
2853+
interface MCPAddServerParams {
2854+
/**
2855+
* The MCP server name. Must be unique among already-configured servers.
2856+
*/
2857+
name: string;
2858+
2859+
/**
2860+
* The command to run (stdio transport).
2861+
*/
2862+
command?: string;
2863+
2864+
/**
2865+
* The command arguments (stdio transport).
2866+
*/
2867+
args?: string[];
2868+
2869+
/**
2870+
* Environment variables for the spawned process (stdio transport).
2871+
*/
2872+
env?: { [key: string]: string };
2873+
2874+
/**
2875+
* The URL (Streamable HTTP transport).
2876+
*/
2877+
url?: string;
2878+
2879+
/**
2880+
* HTTP headers to send with each request (Streamable HTTP transport).
2881+
*/
2882+
headers?: { [key: string]: string };
2883+
2884+
/**
2885+
* Pre-registered OAuth client id (Streamable HTTP transport).
2886+
*/
2887+
clientId?: string;
2888+
2889+
/**
2890+
* Pre-registered OAuth client secret (Streamable HTTP transport).
2891+
*/
2892+
clientSecret?: string;
2893+
2894+
/**
2895+
* Local OAuth callback port (Streamable HTTP transport).
2896+
*/
2897+
oauthPort?: number;
2898+
2899+
/**
2900+
* Whether the new server should be created in a disabled state.
2901+
*/
2902+
disabled?: boolean;
2903+
2904+
/**
2905+
* Where to persist the new server entry. Defaults to "global"
2906+
* (`~/.config/eca/config.json`). Use "workspace" to persist to the
2907+
* workspace's `.eca/config.json` (requires `workspaceUri`).
2908+
*/
2909+
scope?: 'global' | 'workspace';
2910+
2911+
/**
2912+
* Required when `scope = "workspace"`: identifies which workspace folder's
2913+
* `.eca/config.json` should receive the new entry.
2914+
*/
2915+
workspaceUri?: string;
2916+
}
2917+
```
2918+
2919+
_Response:_
2920+
2921+
* result: `MCPAddServerResult` defined as follows:
2922+
2923+
```typescript
2924+
interface MCPAddServerResult {
2925+
/**
2926+
* The newly added server (canonical shape, identical to what
2927+
* `tool/serverUpdated` will subsequently broadcast). Present on success.
2928+
*/
2929+
server?: MCPServerUpdatedParams;
2930+
2931+
/**
2932+
* Present when the add failed (e.g. duplicate name, invalid config,
2933+
* missing workspaceUri).
2934+
*/
2935+
error?: {
2936+
code: string;
2937+
message: string;
2938+
data?: any;
2939+
};
2940+
}
2941+
```
2942+
2943+
### Remove MCP Server (↩️)
2944+
2945+
Removes an MCP server definition at runtime: stops the server if running, deletes the
2946+
entry from the owning config file (global or workspace-local), clears any stored OAuth
2947+
credentials, and emits a `tool/serverRemoved` notification so clients can drop the
2948+
row from their UI.
2949+
2950+
_Request:_
2951+
2952+
* method: `mcp/removeServer`
2953+
* params: `MCPRemoveServerParams` defined as follows:
2954+
2955+
```typescript
2956+
interface MCPRemoveServerParams {
2957+
/**
2958+
* The MCP server name.
2959+
*/
2960+
name: string;
2961+
}
2962+
```
2963+
2964+
_Response:_
2965+
2966+
* result: `MCPRemoveServerResult` defined as follows:
2967+
2968+
```typescript
2969+
interface MCPRemoveServerResult {
2970+
/**
2971+
* The name of the removed server. Present on success.
2972+
*/
2973+
name?: string;
2974+
2975+
/**
2976+
* `true` when the entry was removed from the config file. Present on success.
2977+
*/
2978+
removed?: boolean;
2979+
2980+
/**
2981+
* Present when the remove failed (e.g. unknown name).
2982+
*/
2983+
error?: {
2984+
code: string;
2985+
message: string;
2986+
data?: any;
2987+
};
2988+
}
2989+
```
2990+
28202991
## Provider Management
28212992

28222993
### List Providers (↩️)

resources/ECA_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.134.1
1+
0.134.2

src/eca/features/chat/tool_calls.clj

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[eca.features.chat.lifecycle :as lifecycle]
55
[eca.features.hooks :as f.hooks]
66
[eca.features.tools :as f.tools]
7+
[eca.features.tools.agent :as f.tools.agent]
78
[eca.features.tools.mcp :as f.tools.mcp]
89
[eca.features.tools.util :as tools.util]
910
[eca.llm-util :as llm-util]
@@ -665,18 +666,25 @@
665666
:as resolved-tool} (f.tools/resolve-tool full-name all-tools)
666667
full-name (or (:full-name resolved-tool) full-name)
667668
server-name (:name server)
669+
spawn-agent? (and (= :native origin)
670+
(= "eca" server-name)
671+
(= "spawn_agent" name))
672+
arguments (if parameters
673+
(tools.util/omit-optional-empty-string-args parameters (:arguments tool-call))
674+
(:arguments tool-call))
668675
tool-call (assoc tool-call
669676
:full-name full-name
670-
:arguments (if parameters
671-
(tools.util/omit-optional-empty-string-args parameters (:arguments tool-call))
672-
(:arguments tool-call)))
677+
:arguments (cond-> arguments
678+
spawn-agent? f.tools.agent/normalize-arguments))
673679
decision-plan (decide-tool-call-action
674680
tool-call all-tools @db* config agent chat-id
675681
{:on-before-hook-action (partial lifecycle/notify-before-hook-action! chat-ctx)
676682
:on-after-hook-action (partial lifecycle/notify-after-hook-action! chat-ctx)
677683
:trust (get-in @db* [:chats chat-id :trust])})
678-
{:keys [decision arguments hook-rejected? reason hook-continue
684+
{:keys [decision hook-rejected? reason hook-continue
679685
hook-stop-reason arguments-modified?]} decision-plan
686+
arguments (cond-> (:arguments decision-plan)
687+
spawn-agent? f.tools.agent/normalize-arguments)
680688
_ (when arguments-modified?
681689
(lifecycle/send-content! chat-ctx :system {:type :hookActionFinished
682690
:action-type "shell"

src/eca/features/tools.clj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,10 @@
256256
:call-state-fn call-state-fn
257257
:state-transition-fn state-transition-fn
258258
:trust trust})
259-
(f.mcp/call-tool! tool-name arguments {:db db
260-
:db* db*
261-
:config config
262-
:metrics metrics})))
259+
(f.mcp/call-tool! server-name tool-name arguments {:db db
260+
:db* db*
261+
:config config
262+
:metrics metrics})))
263263
(tools.util/maybe-truncate-output config tool-call-id))]
264264
(logger/debug logger-tag "Tool call result: " result)
265265
(metrics/count-up! "tool-called" {:name resolved-full-name :error (:error result)} metrics)

src/eca/features/tools/agent.clj

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010
(set! *warn-on-reflection* true)
1111

1212
(def ^:private logger-tag "[AGENT-TOOL]")
13+
(def ^:private activity-summary-max-length 40)
14+
15+
(defn normalize-arguments
16+
"Normalize spawn_agent arguments before display, history, and invocation."
17+
[arguments]
18+
(let [activity (when (string? (get arguments "activity"))
19+
(-> (get arguments "activity")
20+
str/trim
21+
(str/replace #"\s+" " ")))]
22+
(if (str/blank? activity)
23+
(dissoc arguments "activity")
24+
(assoc arguments "activity" (if (> (count activity) activity-summary-max-length)
25+
(str (subs activity 0 activity-summary-max-length) "...")
26+
activity)))))
1327

1428
(defn ^:private all-agents
1529
[config]
@@ -64,7 +78,9 @@
6478
:name "spawn_agent"
6579
:server "eca"
6680
:origin "native"
67-
:summary (format "%s: %s" agent-name activity)
81+
:summary (if activity
82+
(format "%s: %s" agent-name activity)
83+
agent-name)
6884
:arguments arguments
6985
:details (cond-> {:type :subagent
7086
:subagent-chat-id subagent-chat-id
@@ -107,9 +123,10 @@
107123
"Handler for the spawn_agent tool.
108124
Spawns a subagent to perform a focused task and returns the result."
109125
[arguments {:keys [db* config messenger metrics chat-id tool-call-id call-state-fn trust]}]
110-
(let [agent-name (get arguments "agent")
126+
(let [arguments (normalize-arguments arguments)
127+
agent-name (get arguments "agent")
111128
task (get arguments "task")
112-
activity (get arguments "activity" "working")
129+
activity (get arguments "activity")
113130
db @db*
114131

115132
;; Check for nesting - prevent subagents from spawning other subagents
@@ -267,12 +284,13 @@
267284
:description "Optional sub-agent model override. Reserved for explicit user override only. Omit unless the user explicitly named a model."}
268285
"variant" {:type "string"
269286
:description "Optional sub-agent model variant override. Reserved for explicit user override only. Omit unless the user explicitly named a variant."}}
270-
:required ["agent" "task" "activity"]}
287+
:required ["agent" "task"]}
271288
:handler #'spawn-agent
272289
:summary-fn (fn [{:keys [args]}]
273290
(if-let [agent-name (get args "agent")]
274-
(let [activity (get args "activity" "working")]
275-
(format "%s: %s" agent-name activity))
291+
(if-let [activity (get (normalize-arguments args) "activity")]
292+
(format "%s: %s" agent-name activity)
293+
agent-name)
276294
"Spawning agent"))}})
277295

278296
(defmethod tools.util/tool-call-details-before-invocation :spawn_agent

src/eca/features/tools/mcp.clj

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -897,13 +897,11 @@
897897
(do-call-tool new-client name arguments nil)
898898
(tool-call-error (format "Failed to re-initialize MCP server '%s'" server-name))))
899899

900-
(defn call-tool! [name arguments {:keys [db db* config metrics]}]
901-
(if-let [[server-name mcp-client needs-reinit?*]
902-
(->> (:mcp-clients db)
903-
(keep (fn [[sn {:keys [client tools needs-reinit?*]}]]
904-
(when (some #(= name (:name %)) tools)
905-
[sn client needs-reinit?*])))
906-
first)]
900+
(defn call-tool! [server-name name arguments {:keys [db db* config metrics]}]
901+
(if-let [[mcp-client needs-reinit?*]
902+
(when-let [{:keys [client tools needs-reinit?*]} (get-in db [:mcp-clients server-name])]
903+
(when (some #(= name (:name %)) tools)
904+
[client needs-reinit?*]))]
907905
(if (and needs-reinit?* @needs-reinit?* db* config metrics)
908906
;; Already flagged — reinit before attempting the call
909907
(reinit-and-call-tool! server-name mcp-client db* config metrics name arguments)
@@ -918,7 +916,7 @@
918916
(reinit-and-call-tool! server-name mcp-client db* config metrics name arguments)
919917

920918
:else result)))
921-
(tool-call-error (format "Tool '%s' not found in any connected MCP server" name))))
919+
(tool-call-error (format "Tool '%s' not found in MCP server '%s'" name server-name))))
922920

923921
(defn all-prompts [db]
924922
(into []

src/eca/features/tools/mcp/clojure_mcp.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
(when (not= "0.1.0" (:version server))
1010
(when ask-approval?
1111
(let [path (get args "file_path")
12-
{:keys [error contents]} (f.mcp/call-tool! name (assoc args "dry_run" "new-source") {:db db})]
12+
{:keys [error contents]} (f.mcp/call-tool! (:name server) name (assoc args "dry_run" "new-source") {:db db})]
1313
(when-not error
1414
(when-let [new-source (some->> contents (filter #(= :text (:type %))) first :text)]
1515
(let [{:keys [added removed diff]} (diff/diff (if new-file?

src/eca/llm_providers/openai.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@
183183
{:type "function"
184184
:name (:full-name tool)
185185
:description (:description tool)
186-
:parameters (:parameters tool)})
186+
:parameters (:parameters tool)
187+
:strict false})
187188
tools)
188189
web-search (conj {:type "web_search"})
189190
image-generation (conj {:type "image_generation" :output_format "png"})))

0 commit comments

Comments
 (0)