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
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}