Skip to content

Commit 008d2da

Browse files
ericdalloeca-agent
andcommitted
Fall back to title/name for missing MCP tool description
Per the MCP spec, tool `description` is optional; some servers omit it or send an empty string. Anthropic's API rejects this with a 400 (`tools.<n>.custom.description: Input should be a valid string`), breaking chats whenever such a server is enabled. Coerce at the MCP boundary in `tool->internal` to `:description -> :title -> "MCP tool: <name>"` so every provider downstream receives a non-null string. 🤖 Generated with [eca](https://eca.dev) Co-Authored-By: eca-agent <git@eca.dev>
1 parent 8adc38d commit 008d2da

3 files changed

Lines changed: 47 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Bugfix: MCP tools without a `description` (which the MCP spec marks optional) no longer break Anthropic chat requests with `tools.<n>.custom.description: Input should be a valid string`. Missing/empty descriptions now fall back to the tool's `title`, then to a synthesized `MCP tool: <name>` string at the MCP boundary so all providers receive a non-null string.
6+
57
## 0.131.0
68

79
- Add `${plugin:root}` dynamic interpolation for plugin-provided config, hooks, commands, and rules.

src/eca/features/tools/mcp.clj

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,16 @@
226226
:else nil)))
227227

228228
(defn ^:private tool->internal
229-
"Adapt plumcp tool map to ECA's internal tool shape."
229+
"Adapt plumcp tool map to ECA's internal tool shape.
230+
231+
Per the MCP spec, tool `description` is optional; some servers omit it. We
232+
normalize a missing/empty description here so providers that require a
233+
non-null string (e.g. Anthropic) don't reject the request."
230234
[tool]
231235
{:name (:name tool)
232-
:description (:description tool)
236+
:description (or (not-empty (:description tool))
237+
(not-empty (:title tool))
238+
(str "MCP tool: " (:name tool)))
233239
:parameters (:inputSchema tool)})
234240

235241
(defn ^:private format-jsonrpc-error

test/eca/features/tools/mcp_test.clj

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,3 +428,40 @@
428428
(is (= 2 @refresh-count*) "should have tried token refresh on attempts 1 and 2")
429429
(is (= :failed (get-in @db* [:mcp-clients "fail-server" :status]))
430430
"server should be in failed state after exhausting retries"))))
431+
432+
(deftest tool->internal-description-fallback-test
433+
(testing "uses :description when present"
434+
(is (= "Generate an image"
435+
(:description (#'mcp/tool->internal
436+
{:name "create-image"
437+
:description "Generate an image"
438+
:inputSchema {}})))))
439+
440+
(testing "falls back to :title when :description is missing"
441+
(is (= "Create Image"
442+
(:description (#'mcp/tool->internal
443+
{:name "create-image"
444+
:title "Create Image"
445+
:inputSchema {}})))))
446+
447+
(testing "falls back to :title when :description is empty string"
448+
(is (= "Create Image"
449+
(:description (#'mcp/tool->internal
450+
{:name "create-image"
451+
:description ""
452+
:title "Create Image"
453+
:inputSchema {}})))))
454+
455+
(testing "synthesizes from :name when both :description and :title are missing"
456+
(is (= "MCP tool: create-image"
457+
(:description (#'mcp/tool->internal
458+
{:name "create-image"
459+
:inputSchema {}})))))
460+
461+
(testing "synthesizes from :name when both :description and :title are empty"
462+
(is (= "MCP tool: edit-image"
463+
(:description (#'mcp/tool->internal
464+
{:name "edit-image"
465+
:description ""
466+
:title ""
467+
:inputSchema {}}))))))

0 commit comments

Comments
 (0)