A tool, resource or prompt handler can stop to ask the client something:
the user (elicitation), the client's model (sampling), or the client's
roots. The handler returns server/input-required with the requests, and is
called again with the answers.
(require '[presa.client :as client]
'[presa.input :as input]
'[presa.protocol :as protocol]
'[presa.server :as server])
(def greet
{:name "greet"
:description "Greets whoever the user names"
:handler (fn [_ ctx]
(if-let [{:strs [action content]} (server/input-response ctx "who")]
(if (= "accept" action)
(str "Hello, " (get content "name") "!")
(server/tool-error "No name given"))
(server/input-required
{"who" (input/elicit "Who should I greet?"
{"type" "object"
"properties" {"name" {"type" "string"}}
"required" ["name"]})})))})
presa.input builds the requests: elicit (a form), elicit-url (a link
for the user to visit), sample (messages for the client's model) and
roots. The handler sees the answers with server/input-response (or all
of them as (:input-responses context)), and answers can be partial: ask
again for what is missing.
The request ends with an input_required result. The client answers on a
new request with inputResponses and the server's requestState, and the
handler runs again. presa:
requiredCapabilities when one is missing;input-required) into
requestState with HMAC-SHA256, scoped to the method, the params and the
caller, with an expiry (:state-ttl-ms, default 300000). A changed,
expired or misplaced token is -32602. :state-secret sets the key; the
default is random per server map, so tokens do not survive a restart;ttlMs: 0 on a resources/read result that took input.Multi-round flows put what they need in the state:
(def two-steps
{:name "two-steps"
:description "Asks twice"
:handler (fn [_ ctx]
(case (get (:state ctx) "step")
nil (server/input-required {"name" (input/elicit "Name?" {"type" "object"})} {"step" 1})
1 (server/input-required {"color" (input/elicit "Color?" {"type" "object"})}
{"step" 2 "name" (get-in (server/input-response ctx "name") ["content" "name"])})
2 (str (get (:state ctx) "name") " likes "
(get-in (server/input-response ctx "color") ["content" "color"]))))})
Those revisions have no input_required. presa sends each input request to
the client as a request from server to client (elicitation/create and
friends), waits for the answers, and calls the handler again, all within
the one tools/call. The same handler serves both eras.
A presa client answers with its :handlers, in both eras:
(def srv (server/server {:name "t" :version "1" :versions protocol/versions :tools [greet two-steps]}))
(def answers
{"elicitation/create" (fn [{:strs [message]}]
{"action" "accept"
"content" (case message
"Who should I greet?" {"name" "Ada"}
"Name?" {"name" "Ada"}
"Color?" {"color" "blue"})})})
(def c (client/connect (client/in-process srv) {:handlers answers}))
(client/text (client/call-tool c "greet"))
;; => "Hello, Ada!"
(client/text (client/call-tool c "two-steps"))
;; => "Ada likes blue"
(client/close c)
(def old (client/connect (client/in-process srv) {:versions ["2025-11-25"] :handlers answers}))
(client/text (client/call-tool old "greet"))
;; => "Hello, Ada!"
(client/close old)
A client that has no handler for a kind of input does not declare that
capability, so the server refuses to ask: -32021 (ex-info :kind :protocol)
on 2026-07-28, an isError result on initialize-era sessions. A client that
declares a capability in :capabilities without a handler for it gets
ex-info with :kind :unsupported when the server asks.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |