Skip to main content

Chapter 23: Advanced Communication: Elicitation

TL;DR

  • What: Elicitation allows your backend tool to pause and ask the user for input mid-execution.
  • Simple Method (Auto-Forms):
    1. From your backend tool, return an elicit response.
    2. In the response, provide a requested_schema using JSON Schema to define the form fields you need.
    3. The Worka Host will auto-generate a form, show it to the user, and send their input back to your tool.
  • Advanced Method (Custom UI):
    1. From your backend, return an elicit response with a custom handlerType in the _meta field (e.g., "handlerType": "my-pack:custom-oauth").
    2. In your frontend UI, use the useElicit('my-pack:custom-oauth', MyCustomComponent) hook to register a React component for that handler type.
    3. The Host will render your custom component in a modal instead of the auto-generated form.

Sometimes, a backend tool needs more information from the user after it has already started running. For example, it might need an API key, a confirmation to proceed with a destructive action, or a choice from a dynamic list of options.

The Elicitation protocol is Worka's powerful solution for this. It allows a backend tool to pause its execution, request input from the user via the frontend, and then resume its work once that input is provided.

The Simple Way: Auto-Generated Forms

The easiest way to get user input is to let the Worka Host build the user interface for you. You do this by providing a schema for the data you need.

Step 1: The Backend Request

In your MCP server tool function, when you determine you need user input, you construct and return an elicit response. The key property of this response is requested_schema, where you define the inputs you need using the JSON Schema standard.

Example (conceptual Rust):

async fn configure_service(req: ToolRequest) -> ToolResponse {
// ... logic ...

// We need an API key and a region from the user.
let schema = json!({
"type": "object",
"properties": {
"api_key": { "type": "string", "description": "Your API Key" },
"region": { "type": "string", "enum": ["us-east-1", "eu-west-2"] }
},
"required": ["api_key"]
});

// Instead of ToolResponse::success, we elicit.
return ToolResponse::elicit(CreateElicitationRequestParam {
message: "Please configure the service".to_string(),
requested_schema: Some(schema),
// ...
});
}

Step 2: The Host Renders the Form

The Worka Host receives this elicit response. It inspects the requested_schema and dynamically generates a clean, user-friendly form with an "API Key" text input, a "Region" dropdown, and a submit button.

Step 3: Resuming Execution

When the user fills out the form and clicks submit, the Host sends the collected data back to your MCP server as the successful result of the elicitation. Your tool function, which was paused waiting for the elicitation to complete, now resumes with the data it needed to continue its work.

The Advanced Way: Custom UIs with useElicit

Sometimes, a generic form isn't enough. You might need a complex, multi-step OAuth flow, a custom file picker, or just a more branded UI. For this, you can provide your own React component to handle the elicitation.

Step 1: The Backend Request (with Handler Type)

This time, your backend sends an elicit request without a requested_schema. Instead, you add a _meta field containing a unique handlerType string.

Example (conceptual Rust):

// ...
let meta = Meta(serde_json::Map::from_iter(vec![(
"handlerType".to_string(),
json!("my-pack:custom-oauth-flow"),
)]));

return ToolResponse::elicit(CreateElicitationRequestParam {
message: "Please connect your account".to_string(),
meta: Some(meta),
// No requested_schema this time
});

Step 2: The Frontend Handler

In your pack's UI code (in a component that is always mounted, like your main view), you use the useElicit hook from the @worka-ai/sdk to register a handler.

import { useElicit } from '@worka-ai/sdk';
import { MyCustomOAuthComponent } from './components/MyCustomOAuthComponent';

function MyPackMainView() {
// Register our component to handle this specific type of elicitation
useElicit('my-pack:custom-oauth-flow', MyCustomOAuthComponent);

return (
// ... a
);
}

Now, when the Host receives an elicitation with the my-pack:custom-oauth-flow handler type, it will find your registered MyCustomOAuthComponent and render it inside a modal, passing it all the necessary props to handle the request and send a response back to the backend.