1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Net-new code for Phase 90 OAPI-01 (HttpConnector trait + HttpClient) +
// OAPI-03 (HttpAuthProvider + AuthConfig six modes). SHAPE lifted from the SQL
// connector analog (`crate::sql`); BODY lifted from the pmcp-run OpenAPI
// reference (`mcp-openapi-server-core`): the reference HTTP client + the shared
// `mcp-server-common` auth providers. The lift replaces the `mcp_server_common`
// path-dependency with toolkit-owned types.
//! HTTP backend primitives for config-driven OpenAPI MCP servers.
//!
//! This module is the backend seam the single-call synthesizer (Plan 03), the
//! code-mode executor (Plan 04), and the binary dispatch (Plan 06) build on. It
//! mirrors [`crate::sql`] in shape:
//!
//! - [`HttpConnector`] — the `#[async_trait] Send + Sync + 'static` trait that
//! executes a REST [`Operation`] and returns JSON (analog of `SqlConnector`).
//! - [`HttpConnectorError`] — the `#[non_exhaustive]` error enum whose `Display`
//! reaches MCP clients and therefore MUST NOT echo credentials or URLs (analog
//! of `ConnectorError`, mirrors its Connection Security doc-comment).
//! - [`Operation`] / [`Parameter`] / [`ParameterLocation`] — the request model
//! the trait signature needs. AUTHORITATIVE in [`schema`] (the `openapiv3`
//! parser is their producer) and re-exported here so the trait signature and
//! every later plan reference one stable type path (Plan 03 / OAPI-02).
//! - [`join_url`] — the ONE shared `base_url` + `path` concatenation helper. Both
//! [`client::HttpClient`] (this plan) and Plan 04's `HttpCodeExecutor` call it
//! instead of re-inlining the trim logic — it preserves an API-Gateway stage
//! prefix (`/v1`) where `Url::join` would silently drop it (Pitfall 2).
//!
//! The whole module is gated behind the opt-in `http` feature so the curated /
//! no-`http` toolkit build stays light (RESEARCH Pitfall 4).
// Why: HTTP method names ("GET", "POST") and product nouns ("OpenAPI") are
// proper nouns / acronyms clippy::doc_markdown otherwise flags for back-ticks.
use async_trait;
use Error;
/// Authentication providers for OUTGOING HTTP requests (OAPI-03 / D-05).
/// reqwest-backed [`HttpConnector`] implementation (OAPI-01).
/// OpenAPI schema parsing seam — forward stub filled by Plan 03 (OAPI-02).
pub use ;
pub use ;
// Operation / Parameter / ParameterLocation are AUTHORITATIVE in `schema` (the
// parser is their producer). They are re-exported here so the
// [`HttpConnector::execute`] trait signature and every Plan (01/03/04/05) keep
// referencing ONE stable type path — the type never moves home again (Codex
// MEDIUM: keep `Operation` in one place from day one).
pub use ;
/// Concatenate a base URL and a request path with exactly one separating slash,
/// PRESERVING any non-root path already on the base.
///
/// This is the ONE shared URL-join helper for the `http` module (de-dup: both
/// [`client::HttpClient`] and Plan 04's `HttpCodeExecutor` call it). It is
/// deliberately NOT `Url::join`, which follows RFC 3986 and treats an absolute
/// request path (e.g. `/users`) as REPLACING the base path — that silently drops
/// an API-Gateway stage prefix like `/v1` (Pitfall 2 / T-90-01-05).
///
/// # Examples
///
/// ```
/// # // join_url is pub(crate); the behaviour is asserted in the module tests.
/// // join_url("https://x/v1", "/users") == "https://x/v1/users"
/// ```
pub
/// Errors an [`HttpConnector`] implementation may surface.
///
/// The enum is `#[non_exhaustive]` so later plans can add failure modes
/// additively without a semver break (mirrors [`crate::sql::ConnectorError`]).
///
/// # Security
///
/// The inner `String` of every variant reaches MCP clients via `Display`.
/// Implementors MUST NOT include the request URL, an `Authorization` header
/// value, a bearer token, or an `app_key` in any inner `String` — those are
/// credentials or capability-bearing locators. Construct error messages from
/// non-secret context only (status code, a static reason). This mirrors the
/// `ConnectorError::Connection` discipline in `sql/mod.rs` (T-90-01-01).
/// Backend-agnostic HTTP connector trait (OAPI-01).
///
/// The analog of [`crate::sql::SqlConnector`] for REST backends: an
/// implementation executes an [`Operation`] against a configured base URL and
/// returns the response body as JSON. [`base_url`](HttpConnector::base_url) is
/// the analog of `SqlConnector::dialect()` — a cheap accessor used by the
/// synthesizer / prompt assembly.
///
/// # Example
///
/// A minimal connector. The example defines a LOCAL dummy struct so the doctest
/// does not depend on any downstream crate (mirrors the `SqlConnector` doctest).
///
/// ```no_run
/// use pmcp_server_toolkit::http::{HttpConnector, HttpConnectorError, Operation};
/// use async_trait::async_trait;
/// use serde_json::Value;
///
/// struct Dummy;
///
/// #[async_trait]
/// impl HttpConnector for Dummy {
/// fn base_url(&self) -> &str { "https://api.example.com/v1" }
/// async fn execute(&self, _operation: &Operation, _args: &Value)
/// -> Result<Value, HttpConnectorError> {
/// Ok(Value::Null)
/// }
/// }
/// ```