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
79impl Payload {
80 /// Which CLI-hosted MCP server this payload targets. `Some` for
81 /// the MCP-routed variants; `None` for `ReadMessageQueue` which
82 /// hits the CLI's own local state.
83 pub fn mcp_kind(&self) -> Option<super::super::McpKind> {
84 match self {
85 Payload::Initialize { mcp_kind, .. }
86 | Payload::ToolsList { mcp_kind, .. }
87 | Payload::ToolsCall { mcp_kind, .. }
88 | Payload::ResourcesList { mcp_kind, .. }
89 | Payload::ResourcesRead { mcp_kind, .. }
90 | Payload::SessionTerminate { mcp_kind } => Some(mcp_kind.clone()),
91 Payload::ReadMessageQueue(_) => None,
92 }
93 }
94}
95
96/// Parameters for [`Payload::ReadMessageQueue`].
97///
98/// Two-rule predicate (now that PENDING is gone):
99/// 1. Direct hit — `message_queue.agent_instance_hierarchy =
100/// agent_instance_hierarchy`.
101/// 2. BOUND-tag hit — `message_queue.agent_tag` resolves to a tag
102/// whose `tags.agent_instance_hierarchy = agent_instance_hierarchy`.
103///
104/// The conduit-side spawn-with-tag flow pre-fires the tag-group
105/// upgrade ahead of every read, so any tags sharing the spawn's
106/// group become BOUND before the EXISTS-check runs and feed
107/// straight into rule 2.
108///
109/// Returns rows oldest-first (`message_queue.id ASC`, which also
110/// matches `message_queue.enqueued_at` ascending due to
111/// AUTOINCREMENT). Row deletion is the downstream consumer's job:
112/// the API stamps the consumed ids onto the first emitted
113/// `AssistantResponseChunk.request_message_ids` so the consumer
114/// knows which rows it owns.
115#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
116#[schemars(rename = "client_objectiveai_mcp.server_request.ReadMessageQueueRequest")]
117pub struct ReadMessageQueueRequest {
118 pub agent_instance_hierarchy: String,
119}
120
121/// Parameters for [`Payload::Initialize`].
122///
123/// Carries plugin arguments lifted off the inbound URL's query
124/// string (`?key=value&flag` → `{"key": Some("value"), "flag": None}`).
125/// Empty for [`super::super::McpKind::ObjectiveAi`] (the primary
126/// upstream takes no args).
127#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
128#[schemars(rename = "client_objectiveai_mcp.server_request.InitializeRequest")]
129pub struct InitializeRequest {
130 /// Plugin arguments the CLI passes through to
131 /// `<plugin> mcp <mcp_name> begin --<key> [value]`. `None` value
132 /// means presence-only flag (`--key`); `Some(v)` means `--key v`.
133 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
134 #[schemars(extend("omitempty" = true))]
135 pub args: IndexMap<String, Option<String>>,
136}