objectiveai_sdk/client_objectiveai_mcp/server_request/payload.rs
1use indexmap::IndexMap;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5/// Tagged union of every JSON-RPC request the API forwards to the
6/// client over the reverse-attach WS. Variant names follow the same
7/// snake_case convention `client_request::Payload` uses; the
8/// `serde(tag = "type")` discriminator pairs with
9/// [`super::super::server_response::Payload`] by name.
10///
11/// MCP-routed variants carry `mcp_kind` directly on the variant
12/// (alongside the typed params via `#[serde(flatten)]`). The non-MCP
13/// `ReadMessageQueue` variant doesn't carry `mcp_kind` at all — it
14/// hits the CLI's own local state and never routes to an upstream
15/// MCP server. Use [`Payload::mcp_kind`] to retrieve it generically.
16#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
17#[schemars(rename = "client_objectiveai_mcp.server_request.Payload")]
18#[serde(tag = "type", rename_all = "snake_case")]
19pub enum Payload {
20 /// POST `initialize`. The proxy's `protocolVersion` doesn't ride
21 /// across this hop — the API discards it on the way in and
22 /// substitutes its own `canonical_initialize_result` on the way
23 /// out. The variant carries the plugin arguments the CLI needs at
24 /// dial time (parsed by the API off the URL query string).
25 #[schemars(title = "Initialize")]
26 Initialize {
27 mcp_kind: super::super::McpKind,
28 #[serde(flatten)]
29 params: InitializeRequest,
30 },
31
32 /// POST `tools/list`.
33 #[schemars(title = "ToolsList")]
34 ToolsList {
35 mcp_kind: super::super::McpKind,
36 #[serde(flatten)]
37 params: crate::mcp::tool::ListToolsRequest,
38 },
39
40 /// POST `tools/call`.
41 #[schemars(title = "ToolsCall")]
42 ToolsCall {
43 mcp_kind: super::super::McpKind,
44 #[serde(flatten)]
45 params: crate::mcp::tool::CallToolRequestParams,
46 },
47
48 /// POST `resources/list`.
49 #[schemars(title = "ResourcesList")]
50 ResourcesList {
51 mcp_kind: super::super::McpKind,
52 #[serde(flatten)]
53 params: crate::mcp::resource::ListResourcesRequest,
54 },
55
56 /// POST `resources/read`.
57 #[schemars(title = "ResourcesRead")]
58 ResourcesRead {
59 mcp_kind: super::super::McpKind,
60 #[serde(flatten)]
61 params: crate::mcp::resource::ReadResourceRequestParams,
62 },
63
64 /// `DELETE` on the routed MCP URL — the proxy closing the
65 /// session. No body beyond `mcp_kind`.
66 #[schemars(title = "SessionTerminate")]
67 SessionTerminate { mcp_kind: super::super::McpKind },
68
69 /// Read the CLI's local message queue for a given agent
70 /// hierarchy. Non-MCP — no `mcp_kind`. Non-destructive: the
71 /// API stamps the consumed ids onto the first
72 /// `AssistantResponseChunk.request_message_ids` it emits so
73 /// the downstream consumer owns row deletion; no separate
74 /// release RPC.
75 #[schemars(title = "ReadMessageQueue")]
76 ReadMessageQueue(ReadMessageQueueRequest),
77
78 /// Resolve a `Client` remote from the client's own local storage
79 /// (agent / swarm / function / profile). Non-MCP — no `mcp_kind`.
80 #[schemars(title = "Retrieve")]
81 Retrieve(super::super::retrieve::Request),
82
83 /// Forceful bulk teardown of every upstream connection for one
84 /// objectiveai response id. Non-MCP — no `mcp_kind` (it spans all
85 /// kinds for that response id). The CLI removes the whole
86 /// response-id bucket from its connection registry, which drops
87 /// every connection and kills every plugin subprocess under it.
88 /// Distinct from `SessionTerminate` (graceful per-`mcp_kind` MCP
89 /// `DELETE`); `Drop` is drop = kill.
90 #[schemars(title = "Drop")]
91 Drop(DropRequest),
92}
93
94impl Payload {
95 /// Which CLI-hosted MCP server this payload targets. `Some` for
96 /// the MCP-routed variants; `None` for `ReadMessageQueue` which
97 /// hits the CLI's own local state.
98 pub fn mcp_kind(&self) -> Option<super::super::McpKind> {
99 match self {
100 Payload::Initialize { mcp_kind, .. }
101 | Payload::ToolsList { mcp_kind, .. }
102 | Payload::ToolsCall { mcp_kind, .. }
103 | Payload::ResourcesList { mcp_kind, .. }
104 | Payload::ResourcesRead { mcp_kind, .. }
105 | Payload::SessionTerminate { mcp_kind } => Some(mcp_kind.clone()),
106 Payload::ReadMessageQueue(_) | Payload::Retrieve(_) | Payload::Drop(_) => None,
107 }
108 }
109}
110
111/// Parameters for [`Payload::ReadMessageQueue`].
112///
113/// Two-rule predicate (now that PENDING is gone):
114/// 1. Direct hit — `message_queue.agent_instance_hierarchy =
115/// agent_instance_hierarchy`.
116/// 2. BOUND-tag hit — `message_queue.agent_tag` resolves to a tag
117/// whose `tags.agent_instance_hierarchy = agent_instance_hierarchy`.
118///
119/// The conduit-side spawn-with-tag flow pre-fires the tag-group
120/// upgrade ahead of every read, so any tags sharing the spawn's
121/// group become BOUND before the EXISTS-check runs and feed
122/// straight into rule 2.
123///
124/// Returns rows oldest-first (`message_queue.id ASC`, which also
125/// matches `message_queue.enqueued_at` ascending due to
126/// AUTOINCREMENT). Row deletion is the downstream consumer's job:
127/// the API stamps the consumed ids onto the first emitted
128/// `AssistantResponseChunk.request_message_ids` so the consumer
129/// knows which rows it owns.
130#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
131#[schemars(rename = "client_objectiveai_mcp.server_request.ReadMessageQueueRequest")]
132pub struct ReadMessageQueueRequest {
133 pub agent_instance_hierarchy: String,
134}
135
136/// Parameters for [`Payload::Drop`]. The objectiveai response id whose
137/// entire upstream-connection bucket the CLI should tear down. The id
138/// rides in the payload (not a header) because `Drop` is a control
139/// message identified solely by its argument.
140#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
141#[schemars(rename = "client_objectiveai_mcp.server_request.DropRequest")]
142pub struct DropRequest {
143 pub response_id: String,
144}
145
146/// Parameters for [`Payload::Initialize`].
147///
148/// Carries plugin arguments lifted off the inbound URL's query
149/// string (`?key=value&flag` → `{"key": Some("value"), "flag": None}`).
150/// Empty for [`super::super::McpKind::ObjectiveAi`] (the primary
151/// upstream takes no args).
152#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
153#[schemars(rename = "client_objectiveai_mcp.server_request.InitializeRequest")]
154pub struct InitializeRequest {
155 /// Plugin arguments the CLI passes through to
156 /// `<plugin> mcp <mcp_name> begin --<key> [value]`. `None` value
157 /// means presence-only flag (`--key`); `Some(v)` means `--key v`.
158 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
159 #[schemars(extend("omitempty" = true))]
160 pub args: IndexMap<String, Option<String>>,
161}