Skip to content

ActionContext (C++)

Every handler is called with (Json input, ActionContext context). The context is cheap to copy and every copy talks to the same invocation, including the shared progress ceiling, so a handler can hand one to a helper without losing anything.

context.action_name(); // "addTodo"
context.invocation_id(); // the gateway's id for this call
context.agent(); // { id, name }; "pending" until the session is claimed
context.origin(); // the application's origin
context.route(); // where in the application the agent was, if the gateway said
context.agent_capabilities(); // what the other end negotiated

Check agent_capabilities() before sample or elicit whenever the handler has a useful non-interactive fallback. It saves a round trip that was always going to fail.

The canonical C++ examples report one update for each imported item:

context.progress(ProgressUpdate()
.message(std::to_string(index + 1) + "/" + std::to_string(items.size()) + " imported")
.percent(static_cast<int>((index + 1) * 100 / items.size())));

Every field is optional; send whichever the handler actually knows. Fire-and-forget, like every notification.

Percent is clamped into 0 to 100, and never allowed to fall below a value already sent for this invocation. An agent rendering a progress bar reads a backwards jump as a restart, so a lower value is raised to the ceiling rather than dropped. Two copies of the context handed to two helpers share one ceiling.

The prompts example logs before sampling:

context.log(LogEntry::info("Testing prompt " + identifier));

Four levels are available: debug, info, warn, and error. Logging is fire-and-forget.

Asks the agent's model to answer a prompt. The todo example supplies a schema and a token limit:

SampleRequest request("Produce exactly " + std::to_string(count) +
" concrete todo items for the theme \"" + theme +
"\". Return JSON matching { items: string[] }. Items should be short, "
"imperative, and user-friendly. No numbering.");
request.json_schema(suggested_todos_output_schema()).max_tokens(400);
auto sampled = co_await context.sample(std::move(request));
if (!sampled.ok()) co_return sampled.error();

Sampling depth is not a field in any Tesseron frame. The gateway owns maxSamplingDepth and answers -32008 itself, so the host forwards the request without counting.

A yes-or-no gate in front of something destructive. The prompts example uses it before deleting a prompt:

auto confirmation = co_await context.confirm("Delete prompt \"" + prompt->second.name +
"\" (tested " + std::to_string(prompt->second.times_tested) +
"x)? This cannot be undone.");
if (!confirmation.ok()) co_return confirmation.error();
if (!confirmation.value()) {
co_return Json{{"id", identifier}, {"deleted", false}, {"cancelled", true}};
}

true only means explicit accept. A decline, a cancel, and an agent that never negotiated elicitation all answer false.

Asks the user for structured content. The todo example asks for a replacement name:

ElicitRequest request("Rename \"" + todo->text + "\" to?");
request.json_schema({
{"type", "object"},
{"properties", {{"newName", {{"type", "string"}, {"minLength", 1}}}}},
{"required", {"newName"}},
});
auto elicited = co_await context.elicit(std::move(request));
if (!elicited.ok()) co_return elicited.error();
if (!elicited.value().has_value()) {
co_return Json{{"id", identifier}, {"renamed", false}, {"cancelled", true}};
}

An empty optional means a decline or a cancel. Unlike confirm, a missing capability is an error rather than a default, because structured content has no safe default and the handler has to branch on it.

The schema is checked against the elicitation rules before the frame leaves. MCP renders an elicit prompt as a flat form, so the protocol constrains the schema to a single object of primitive leaves. A top-level oneOf, anyOf, allOf, or not, a top-level type other than object, or a property typed object or array all fail with -32602 at the elicit call site.

stop_token(), cancelled(), and co_await wait_for_cancellation() expose the same signal when the agent cancels, the invocation times out, or the transport closes. See Actions.

Application-thread handoff is covered on the Threading page.