Skip to main content

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    /// Copy a file/folder from one laboratory to another (both on this
94    /// conduit). Non-MCP — it spans TWO laboratories, so it carries their
95    /// ids directly rather than a single `mcp_kind`. The conduit streams
96    /// the source laboratory's `/export` straight into the destination's
97    /// `/import` (a tar splice, no intermediate buffering).
98    #[schemars(title = "LaboratoryTransfer")]
99    LaboratoryTransfer(LaboratoryTransferRequest),
100}
101
102impl Payload {
103    /// Which CLI-hosted MCP server this payload targets. `Some` for
104    /// the MCP-routed variants; `None` for `ReadMessageQueue` which
105    /// hits the CLI's own local state.
106    pub fn mcp_kind(&self) -> Option<super::super::McpKind> {
107        match self {
108            Payload::Initialize { mcp_kind, .. }
109            | Payload::ToolsList { mcp_kind, .. }
110            | Payload::ToolsCall { mcp_kind, .. }
111            | Payload::ResourcesList { mcp_kind, .. }
112            | Payload::ResourcesRead { mcp_kind, .. }
113            | Payload::SessionTerminate { mcp_kind } => Some(mcp_kind.clone()),
114            Payload::ReadMessageQueue(_)
115            | Payload::Retrieve(_)
116            | Payload::Drop(_)
117            | Payload::LaboratoryTransfer(_) => None,
118        }
119    }
120}
121
122/// Parameters for [`Payload::ReadMessageQueue`].
123///
124/// Two-rule predicate (now that PENDING is gone):
125/// 1. Direct hit — `message_queue.agent_instance_hierarchy =
126///    agent_instance_hierarchy`.
127/// 2. BOUND-tag hit — `message_queue.agent_tag` resolves to a tag
128///    whose `tags.agent_instance_hierarchy = agent_instance_hierarchy`.
129///
130/// The conduit-side spawn-with-tag flow pre-fires the tag-group
131/// upgrade ahead of every read, so any tags sharing the spawn's
132/// group become BOUND before the EXISTS-check runs and feed
133/// straight into rule 2.
134///
135/// Returns rows oldest-first (`message_queue.id ASC`, which also
136/// matches `message_queue.enqueued_at` ascending due to
137/// AUTOINCREMENT). Row deletion is the downstream consumer's job:
138/// the API stamps the consumed ids onto the first emitted
139/// `AssistantResponseChunk.request_message_ids` so the consumer
140/// knows which rows it owns.
141#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
142#[schemars(rename = "client_objectiveai_mcp.server_request.ReadMessageQueueRequest")]
143pub struct ReadMessageQueueRequest {
144    pub agent_instance_hierarchy: String,
145}
146
147/// Parameters for [`Payload::Drop`]. The objectiveai response id whose
148/// entire upstream-connection bucket the CLI should tear down. The id
149/// rides in the payload (not a header) because `Drop` is a control
150/// message identified solely by its argument.
151#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
152#[schemars(rename = "client_objectiveai_mcp.server_request.DropRequest")]
153pub struct DropRequest {
154    pub response_id: String,
155}
156
157/// Parameters for [`Payload::LaboratoryTransfer`]. Copy `source_path` from
158/// the `source_id` laboratory into `dest_path` of the `dest_id` laboratory
159/// (cp-style: a directory `source_path` lands as `dest_path/<basename>`).
160#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
161#[schemars(rename = "client_objectiveai_mcp.server_request.LaboratoryTransferRequest")]
162pub struct LaboratoryTransferRequest {
163    pub source_id: String,
164    pub dest_id: String,
165    pub source_path: String,
166    pub dest_path: String,
167}
168
169/// Parameters for [`Payload::Initialize`].
170///
171/// Carries plugin arguments lifted off the inbound URL's query
172/// string (`?key=value&flag` → `{"key": Some("value"), "flag": None}`).
173/// Empty for [`super::super::McpKind::ObjectiveAi`] (the primary
174/// upstream takes no args).
175#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
176#[schemars(rename = "client_objectiveai_mcp.server_request.InitializeRequest")]
177pub struct InitializeRequest {
178    /// Plugin arguments the CLI passes through to
179    /// `<plugin> mcp <mcp_name> begin --<key> [value]`. `None` value
180    /// means presence-only flag (`--key`); `Some(v)` means `--key v`.
181    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
182    #[schemars(extend("omitempty" = true))]
183    pub args: IndexMap<String, Option<String>>,
184}