McpSource.elicitationToRequest
Java · package io.github.muthuishere:toolnexus · SPEC §2 · McpSource.java
// package-private in the Java port — see the note belowstatic Request elicitationToRequest(McpSchema.ElicitRequest params)static McpSchema.ElicitResult answerToElicitResult(Answer answer)When a connected MCP server calls elicitation/create mid-tools/call — asking the human for a
value, or to complete a login at a URL — McpSource.load bridges that reverse-request onto the
one §10 suspension contract: Function<Request, Answer> waitFor. Form-mode elicitation becomes
a Request with kind:"input" (the JSON Schema in data.schema); URL-mode becomes
kind:"authorization" (the link in url). Your waitFor’s Answer is mapped back:
ok:true → accept (content = answer.data()), reason:"declined" → decline, anything else
(cancelled, expired, timed out) → cancel.
When to use it
Section titled “When to use it”You do not call this mapping directly. You use it by passing a waitFor to
McpSource.load or Toolkit.Options.waitFor
— the same resolver you already use for tool-level suspensions (§10). Read this page when you
need to know exactly what shape of Request your waitFor will see for an MCP elicitation, and
exactly how your Answer is interpreted.
Why this and not the alternative
Section titled “Why this and not the alternative”Omit waitFor (or pass null) and the client degrades cleanly: the elicitation capability is
never advertised to the server, so a server that requires it will simply not offer that
interaction — no crash, no dangling suspension.
Examples
Section titled “Examples”1. The Request shapes the bridge produces — form vs. URL
Section titled “1. The Request shapes the bridge produces — form vs. URL”The mapping is pure data-shuffling: kind, prompt, url, and data.schema are the exact
fields SPEC §10 pins across all six ports. This example builds the two shapes by hand to pin the
contract your waitFor must handle — the same shapes McpSource.load’s internal bridge
produces from a real elicitation/create.
import io.github.muthuishere.toolnexus.*;import java.util.List;import java.util.Map;
public class Example { public static void main(String[] args) { // Form mode: kind:"input", the requested schema travels in data.schema. Map<String, Object> schema = Map.of( "type", "object", "properties", Map.of("name", Map.of("type", "string")), "required", List.of("name") ); Request formReq = new Request("elc-1", "input", "Your name?", null, Map.of("schema", schema), null); if (!formReq.kind().equals("input")) throw new AssertionError(formReq.kind()); if (!formReq.data().get("schema").equals(schema)) throw new AssertionError(formReq.data()); if (formReq.url() != null) throw new AssertionError("form mode carries no url");
// URL mode: kind:"authorization", the link travels in url, no schema. Request urlReq = new Request("elc-2", "authorization", "Log in", "https://x/auth", null, null); if (!urlReq.kind().equals("authorization")) throw new AssertionError(urlReq.kind()); if (!urlReq.url().equals("https://x/auth")) throw new AssertionError(urlReq.url()); if (urlReq.data() != null) throw new AssertionError("url mode carries no data");
System.out.println("ok: " + formReq.kind() + " / " + urlReq.kind()); }}2. Wiring waitFor into McpSource.load — the real entry point
Section titled “2. Wiring waitFor into McpSource.load — the real entry point”The mapping function itself is internal; what you actually write is a waitFor and hand it to
load. With every server disabled this is fully hermetic — waitFor is never invoked because
no server connects to elicit anything, but the wiring compiles and runs exactly as it would with
a live, elicitation-capable server.
import io.github.muthuishere.toolnexus.*;import java.util.Map;import java.util.function.Function;
public class Example { public static void main(String[] args) throws Exception { Map<String, Object> config = Map.of( "mcpServers", Map.of("off", Map.of("command", java.util.List.of("x"), "enabled", false)) );
// A real host would prompt a human here. This one always accepts. Function<Request, Answer> waitFor = req -> new Answer(req.id(), true, Map.of("name", "Ada"));
try (McpSource src = McpSource.load(config, waitFor)) { // No connected server ⇒ elicitation never fires ⇒ status is just "disabled". if (!"disabled".equals(src.status().get("off"))) throw new AssertionError(src.status()); System.out.println("ok: waitFor wired, " + src.status()); } }}3. How an Answer maps back — accept, decline, cancel
Section titled “3. How an Answer maps back — accept, decline, cancel”The rule McpSource’s internal answerToElicitResult applies: ok:true → accept;
ok:false with reason:"declined" → decline; anything else ok:false (cancelled, expired,
unspecified) → cancel. This example asserts the documented rule against the public Answer
shape so the contract is checkable without reaching into the package-private function.
import io.github.muthuishere.toolnexus.*;import java.util.Map;
public class Example { // Mirrors the rule documented on McpSource.answerToElicitResult (package-private). static String classify(Answer a) { if (a.ok()) return "accept"; return "declined".equals(a.reason()) ? "decline" : "cancel"; }
public static void main(String[] args) { Answer accepted = new Answer("id-1", true, Map.of("name", "Ada")); Answer declined = new Answer("id-2", false, null, "declined"); Answer expired = new Answer("id-3", false, null, "expired"); Answer noReason = new Answer("id-4", false, null, null);
if (!classify(accepted).equals("accept")) throw new AssertionError(); if (!classify(declined).equals("decline")) throw new AssertionError(); if (!classify(expired).equals("cancel")) throw new AssertionError(); if (!classify(noReason).equals("cancel")) throw new AssertionError();
System.out.println("ok: accept/decline/cancel classification matches the bridge rule"); }}Fields
Section titled “Fields”| Field | What it is |
|---|---|
Request.kind() |
"input" for form-mode elicitation, "authorization" for URL-mode. |
Request.prompt() |
The server’s message, verbatim (empty string if absent). |
Request.data() |
{"schema": <requestedSchema>} for form mode; null for URL mode. |
Request.url() |
The link for URL mode; null for form mode. |
Answer.ok() |
true → MCP accept (content = answer.data(), or {}). |
Answer.reason() |
"declined" → MCP decline; any other non-ok reason (or none) → MCP cancel. |
See also
Section titled “See also”McpSource.load— the entry point that actually wires this bridge viawaitFor.McpSource.loadWith— the samewaitForargument, plus cancellation.McpSource.listMcpTools— list what each configured server would expose, plus per-server status, without wiring it into a toolkit.