@celestia-island/plana-types 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +38 -0
- package/bindings/FileAnchor.ts +3 -0
- package/bindings/engine.ts +277 -0
- package/bindings/enums.ts +31 -0
- package/bindings/httpTypes.ts +197 -0
- package/bindings/index.ts +47 -0
- package/bindings/mcp/aporia.ts +61 -0
- package/bindings/mcp/eleos.ts +18 -0
- package/bindings/mcp/epieikeia.ts +48 -0
- package/bindings/mcp/haplotes.ts +47 -0
- package/bindings/mcp/hubris.ts +41 -0
- package/bindings/mcp/index.ts +13 -0
- package/bindings/mcp/kalos.ts +47 -0
- package/bindings/mcp/neikos.ts +76 -0
- package/bindings/mcp/orexis.ts +64 -0
- package/bindings/mcp/philia.ts +57 -0
- package/bindings/mcp/polemos.ts +55 -0
- package/bindings/mcp/skemma.ts +58 -0
- package/bindings/mcp/skopeo.ts +56 -0
- package/bindings/mcp/webAutomation.ts +31 -0
- package/bindings/model.ts +240 -0
- package/bindings/package.json +16 -0
- package/bindings/region.ts +3 -0
- package/bindings/serde_json/JsonValue.ts +3 -0
- package/bindings/ws/agentLifecycle.ts +41 -0
- package/bindings/ws/auth.ts +15 -0
- package/bindings/ws/baseMessages.ts +7 -0
- package/bindings/ws/bridgeNetwork.ts +71 -0
- package/bindings/ws/core.ts +51 -0
- package/bindings/ws/fileBrowsing.ts +57 -0
- package/bindings/ws/handshake.ts +23 -0
- package/bindings/ws/industrial.ts +72 -0
- package/bindings/ws/knowledgeBase.ts +10 -0
- package/bindings/ws/layer2.ts +25 -0
- package/bindings/ws/llmProvider.ts +74 -0
- package/bindings/ws/logs.ts +11 -0
- package/bindings/ws/malkuth.ts +62 -0
- package/bindings/ws/noa.ts +17 -0
- package/bindings/ws/stateSync.ts +19 -0
- package/bindings/ws/systemUi.ts +5 -0
- package/bindings/ws/tasks.ts +6 -0
- package/bindings/ws/views.ts +163 -0
- package/bindings/ws/workspace.ts +11 -0
- package/bindings/ws/yolo.ts +32 -0
- package/examples/schema_dump.rs +51 -0
- package/package.json +6 -0
- package/pnpm-workspace.yaml +2 -0
- package/src/engine.rs +602 -0
- package/src/enums.rs +334 -0
- package/src/external_mcp.rs +132 -0
- package/src/http.rs +1077 -0
- package/src/identity.rs +160 -0
- package/src/lib.rs +1215 -0
- package/src/malkuth.rs +145 -0
- package/src/mcp/aporia.rs +272 -0
- package/src/mcp/eleos.rs +210 -0
- package/src/mcp/epieikeia.rs +194 -0
- package/src/mcp/haplotes.rs +310 -0
- package/src/mcp/hubris.rs +377 -0
- package/src/mcp/kalos.rs +251 -0
- package/src/mcp/mod.rs +23 -0
- package/src/mcp/neikos.rs +533 -0
- package/src/mcp/orexis.rs +493 -0
- package/src/mcp/philia.rs +267 -0
- package/src/mcp/polemos.rs +241 -0
- package/src/mcp/skemma.rs +442 -0
- package/src/mcp/skopeo.rs +282 -0
- package/src/mcp/web_automation.rs +122 -0
- package/src/model.rs +421 -0
- package/src/protocol/base_messages.rs +125 -0
- package/src/protocol/handshake.rs +364 -0
- package/src/protocol/jsonrpc.rs +888 -0
- package/src/protocol/mod.rs +10 -0
- package/src/rbac.rs +786 -0
- package/src/region.rs +362 -0
- package/src/tracing_helpers.rs +9 -0
- package/src/ws/agent/agent_lifecycle.rs +221 -0
- package/src/ws/agent/layer2.rs +126 -0
- package/src/ws/agent/mod.rs +9 -0
- package/src/ws/agent/state_sync.rs +111 -0
- package/src/ws/agent/tasks.rs +43 -0
- package/src/ws/agent/yolo.rs +161 -0
- package/src/ws/mod.rs +10 -0
- package/src/ws/services/auth.rs +99 -0
- package/src/ws/services/industrial.rs +647 -0
- package/src/ws/services/knowledge_base.rs +59 -0
- package/src/ws/services/llm_provider.rs +371 -0
- package/src/ws/services/mod.rs +7 -0
- package/src/ws/ui/bridge_network.rs +96 -0
- package/src/ws/ui/file_browsing.rs +88 -0
- package/src/ws/ui/logs.rs +55 -0
- package/src/ws/ui/mod.rs +11 -0
- package/src/ws/ui/noa.rs +105 -0
- package/src/ws/ui/system_ui.rs +27 -0
- package/src/ws/ui/views.rs +159 -0
- package/src/ws/ui/workspace.rs +73 -0
package/src/enums.rs
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
//! Shared domain vocabulary enums.
|
|
2
|
+
//!
|
|
3
|
+
//! Platform-wide enumerations consumed by MCP tool types and protocol
|
|
4
|
+
//! messages. These are *vocabulary*, not tool I/O shapes, so they live at the
|
|
5
|
+
//! crate root rather than under `mcp/`. Per-agent tool request/result structs
|
|
6
|
+
//! (see `mcp/`) reference the types defined here.
|
|
7
|
+
|
|
8
|
+
use serde::{Deserialize, Serialize};
|
|
9
|
+
use std::fmt;
|
|
10
|
+
|
|
11
|
+
macro_rules! str_enum {
|
|
12
|
+
($name:ident { $($variant:ident = $val:literal),* $(,)? }) => {
|
|
13
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ts_rs::TS, schemars::JsonSchema)]
|
|
14
|
+
#[ts(export, export_to = "enums.ts")]
|
|
15
|
+
pub enum $name {
|
|
16
|
+
$($variant,)*
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl $name {
|
|
20
|
+
pub fn as_str(&self) -> &'static str {
|
|
21
|
+
match self {
|
|
22
|
+
$($name::$variant => $val,)*
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl fmt::Display for $name {
|
|
28
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
29
|
+
f.write_str(self.as_str())
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl From<$name> for String {
|
|
34
|
+
fn from(v: $name) -> String {
|
|
35
|
+
v.as_str().to_string()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
str_enum!(FileOpStatus {
|
|
42
|
+
Created = "created",
|
|
43
|
+
Deleted = "deleted",
|
|
44
|
+
Edited = "edited",
|
|
45
|
+
Written = "written",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
str_enum!(FileType {
|
|
49
|
+
File = "file",
|
|
50
|
+
Directory = "directory",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
str_enum!(ContainerOpStatus {
|
|
54
|
+
Created = "created",
|
|
55
|
+
Running = "running",
|
|
56
|
+
Stopped = "stopped",
|
|
57
|
+
Removed = "removed",
|
|
58
|
+
Forked = "forked",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
str_enum!(ConsultationStatus {
|
|
62
|
+
WaitingHuman = "waiting_human",
|
|
63
|
+
Pending = "pending",
|
|
64
|
+
Answered = "answered",
|
|
65
|
+
Delivered = "delivered",
|
|
66
|
+
Scheduled = "scheduled",
|
|
67
|
+
Triggered = "triggered",
|
|
68
|
+
Cancelled = "cancelled",
|
|
69
|
+
Replied = "replied",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
str_enum!(WebSearchEngine {
|
|
73
|
+
Duckduckgo = "duckduckgo",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
str_enum!(ScriptLanguage {
|
|
77
|
+
Bash = "bash",
|
|
78
|
+
Sh = "sh",
|
|
79
|
+
Python = "python",
|
|
80
|
+
Python3 = "python3",
|
|
81
|
+
Javascript = "javascript",
|
|
82
|
+
Typescript = "typescript",
|
|
83
|
+
Node = "node",
|
|
84
|
+
Zsh = "zsh",
|
|
85
|
+
Layer2 = "layer2",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
str_enum!(ObservationType {
|
|
89
|
+
Reading = "reading",
|
|
90
|
+
Editing = "editing",
|
|
91
|
+
Deleting = "deleting",
|
|
92
|
+
Watching = "watching",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
str_enum!(FileOperationType {
|
|
96
|
+
Reading = "Reading",
|
|
97
|
+
Editing = "Editing",
|
|
98
|
+
Deleting = "Deleting",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
str_enum!(ConversationStatus {
|
|
102
|
+
Active = "Active",
|
|
103
|
+
Resolved = "Resolved",
|
|
104
|
+
Deadlocked = "Deadlocked",
|
|
105
|
+
Escalated = "Escalated",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
str_enum!(ConversationMessageType {
|
|
109
|
+
Question = "Question",
|
|
110
|
+
Answer = "Answer",
|
|
111
|
+
Clarification = "Clarification",
|
|
112
|
+
Objection = "Objection",
|
|
113
|
+
CounterProposal = "CounterProposal",
|
|
114
|
+
Resolution = "Resolution",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
str_enum!(AnnotationType {
|
|
118
|
+
Note = "note",
|
|
119
|
+
Warning = "warning",
|
|
120
|
+
Todo = "todo",
|
|
121
|
+
Suggestion = "suggestion",
|
|
122
|
+
Conflict = "conflict",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
str_enum!(GoalStatus {
|
|
126
|
+
Active = "active",
|
|
127
|
+
Completed = "completed",
|
|
128
|
+
Abandoned = "abandoned",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
str_enum!(TrackStatus {
|
|
132
|
+
Active = "active",
|
|
133
|
+
Completed = "completed",
|
|
134
|
+
Abandoned = "abandoned",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
str_enum!(GoalTaskStatus {
|
|
138
|
+
Pending = "pending",
|
|
139
|
+
InProgress = "in_progress",
|
|
140
|
+
Completed = "completed",
|
|
141
|
+
Failed = "failed",
|
|
142
|
+
Cancelled = "cancelled",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// How a peer reached this instance — the topology of the link, not the
|
|
146
|
+
// physical medium (which is `evernight::link::LinkType`).
|
|
147
|
+
//
|
|
148
|
+
// `Local` covers both a Windows-native peer and a same-host WSL2 peer; the
|
|
149
|
+
// two are distinguished by a shared-secret handshake (a key file under
|
|
150
|
+
// `%LOCALAPPDATA%\celestia\local-secret`, readable from WSL2 via `/mnt/c/`),
|
|
151
|
+
// not by IP alone. `RemoteLan` is an RFC1918 / link-local peer without that
|
|
152
|
+
// secret; `RemoteInternet` is anything else.
|
|
153
|
+
//
|
|
154
|
+
// This enum is the canonical source of truth (defined here, consumed by
|
|
155
|
+
// entelecheia and shittim-chest). evernight attaches it to its sessions as
|
|
156
|
+
// a routing tag — any authorized session is stamped with how it connected —
|
|
157
|
+
// but evernight itself does not branch on it for its own behaviour.
|
|
158
|
+
str_enum!(ConnectionType {
|
|
159
|
+
Local = "local",
|
|
160
|
+
RemoteLan = "remote_lan",
|
|
161
|
+
RemoteInternet = "remote_internet",
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
#[cfg(test)]
|
|
165
|
+
mod tests {
|
|
166
|
+
use super::*;
|
|
167
|
+
|
|
168
|
+
// ── as_str() / Display / From<String> consistency ──────────────
|
|
169
|
+
//
|
|
170
|
+
// The str_enum! macro generates three traits: as_str(), Display, and
|
|
171
|
+
// From<Enum> for String. All three must agree with the declared wire
|
|
172
|
+
// value. NOTE: serde serialization uses the *PascalCase variant name*
|
|
173
|
+
// (not the as_str() value) — this is documented behavior. The tests
|
|
174
|
+
// below verify both the as_str()/Display/From trio AND the serde
|
|
175
|
+
// representation so the divergence is explicit and guarded.
|
|
176
|
+
|
|
177
|
+
#[test]
|
|
178
|
+
fn file_op_status_as_str_values() {
|
|
179
|
+
assert_eq!(FileOpStatus::Created.as_str(), "created");
|
|
180
|
+
assert_eq!(FileOpStatus::Deleted.as_str(), "deleted");
|
|
181
|
+
assert_eq!(FileOpStatus::Edited.as_str(), "edited");
|
|
182
|
+
assert_eq!(FileOpStatus::Written.as_str(), "written");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[test]
|
|
186
|
+
fn file_type_as_str_values() {
|
|
187
|
+
assert_eq!(FileType::File.as_str(), "file");
|
|
188
|
+
assert_eq!(FileType::Directory.as_str(), "directory");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#[test]
|
|
192
|
+
fn container_op_status_as_str_values() {
|
|
193
|
+
assert_eq!(ContainerOpStatus::Created.as_str(), "created");
|
|
194
|
+
assert_eq!(ContainerOpStatus::Running.as_str(), "running");
|
|
195
|
+
assert_eq!(ContainerOpStatus::Stopped.as_str(), "stopped");
|
|
196
|
+
assert_eq!(ContainerOpStatus::Removed.as_str(), "removed");
|
|
197
|
+
assert_eq!(ContainerOpStatus::Forked.as_str(), "forked");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#[test]
|
|
201
|
+
fn consultation_status_as_str_values() {
|
|
202
|
+
assert_eq!(ConsultationStatus::WaitingHuman.as_str(), "waiting_human");
|
|
203
|
+
assert_eq!(ConsultationStatus::Pending.as_str(), "pending");
|
|
204
|
+
assert_eq!(ConsultationStatus::Answered.as_str(), "answered");
|
|
205
|
+
assert_eq!(ConsultationStatus::Delivered.as_str(), "delivered");
|
|
206
|
+
assert_eq!(ConsultationStatus::Scheduled.as_str(), "scheduled");
|
|
207
|
+
assert_eq!(ConsultationStatus::Triggered.as_str(), "triggered");
|
|
208
|
+
assert_eq!(ConsultationStatus::Cancelled.as_str(), "cancelled");
|
|
209
|
+
assert_eq!(ConsultationStatus::Replied.as_str(), "replied");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[test]
|
|
213
|
+
fn script_language_as_str_values() {
|
|
214
|
+
assert_eq!(ScriptLanguage::Bash.as_str(), "bash");
|
|
215
|
+
assert_eq!(ScriptLanguage::Sh.as_str(), "sh");
|
|
216
|
+
assert_eq!(ScriptLanguage::Python.as_str(), "python");
|
|
217
|
+
assert_eq!(ScriptLanguage::Python3.as_str(), "python3");
|
|
218
|
+
assert_eq!(ScriptLanguage::Javascript.as_str(), "javascript");
|
|
219
|
+
assert_eq!(ScriptLanguage::Typescript.as_str(), "typescript");
|
|
220
|
+
assert_eq!(ScriptLanguage::Node.as_str(), "node");
|
|
221
|
+
assert_eq!(ScriptLanguage::Zsh.as_str(), "zsh");
|
|
222
|
+
assert_eq!(ScriptLanguage::Layer2.as_str(), "layer2");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#[test]
|
|
226
|
+
fn goal_status_as_str_values() {
|
|
227
|
+
assert_eq!(GoalStatus::Active.as_str(), "active");
|
|
228
|
+
assert_eq!(GoalStatus::Completed.as_str(), "completed");
|
|
229
|
+
assert_eq!(GoalStatus::Abandoned.as_str(), "abandoned");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#[test]
|
|
233
|
+
fn track_status_as_str_values() {
|
|
234
|
+
assert_eq!(TrackStatus::Active.as_str(), "active");
|
|
235
|
+
assert_eq!(TrackStatus::Completed.as_str(), "completed");
|
|
236
|
+
assert_eq!(TrackStatus::Abandoned.as_str(), "abandoned");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#[test]
|
|
240
|
+
fn goal_task_status_as_str_values() {
|
|
241
|
+
assert_eq!(GoalTaskStatus::Pending.as_str(), "pending");
|
|
242
|
+
assert_eq!(GoalTaskStatus::InProgress.as_str(), "in_progress");
|
|
243
|
+
assert_eq!(GoalTaskStatus::Completed.as_str(), "completed");
|
|
244
|
+
assert_eq!(GoalTaskStatus::Failed.as_str(), "failed");
|
|
245
|
+
assert_eq!(GoalTaskStatus::Cancelled.as_str(), "cancelled");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[test]
|
|
249
|
+
fn connection_type_as_str_values() {
|
|
250
|
+
assert_eq!(ConnectionType::Local.as_str(), "local");
|
|
251
|
+
assert_eq!(ConnectionType::RemoteLan.as_str(), "remote_lan");
|
|
252
|
+
assert_eq!(ConnectionType::RemoteInternet.as_str(), "remote_internet");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#[test]
|
|
256
|
+
fn annotation_type_as_str_values() {
|
|
257
|
+
assert_eq!(AnnotationType::Note.as_str(), "note");
|
|
258
|
+
assert_eq!(AnnotationType::Warning.as_str(), "warning");
|
|
259
|
+
assert_eq!(AnnotationType::Todo.as_str(), "todo");
|
|
260
|
+
assert_eq!(AnnotationType::Suggestion.as_str(), "suggestion");
|
|
261
|
+
assert_eq!(AnnotationType::Conflict.as_str(), "conflict");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── Display matches as_str ─────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
#[test]
|
|
267
|
+
fn display_matches_as_str() {
|
|
268
|
+
assert_eq!(format!("{}", ContainerOpStatus::Running), "running");
|
|
269
|
+
assert_eq!(
|
|
270
|
+
format!("{}", ConsultationStatus::WaitingHuman),
|
|
271
|
+
"waiting_human"
|
|
272
|
+
);
|
|
273
|
+
assert_eq!(format!("{}", ScriptLanguage::Typescript), "typescript");
|
|
274
|
+
assert_eq!(format!("{}", GoalTaskStatus::InProgress), "in_progress");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ── From<Enum> for String matches as_str ───────────────────────
|
|
278
|
+
|
|
279
|
+
#[test]
|
|
280
|
+
fn from_enum_to_string_matches_as_str() {
|
|
281
|
+
let s: String = ContainerOpStatus::Forked.into();
|
|
282
|
+
assert_eq!(s, "forked");
|
|
283
|
+
let s: String = ConsultationStatus::Replied.into();
|
|
284
|
+
assert_eq!(s, "replied");
|
|
285
|
+
let s: String = ConnectionType::RemoteInternet.into();
|
|
286
|
+
assert_eq!(s, "remote_internet");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── serde uses PascalCase variant name (NOT as_str value) ──────
|
|
290
|
+
//
|
|
291
|
+
// This documents a deliberate divergence: serde_serialize produces
|
|
292
|
+
// the Rust variant identifier (e.g. "WaitingHuman"), while as_str()
|
|
293
|
+
// produces the domain vocabulary string (e.g. "waiting_human").
|
|
294
|
+
// Both are stable contracts — changing either would break consumers.
|
|
295
|
+
|
|
296
|
+
#[test]
|
|
297
|
+
fn serde_serializes_variant_name_not_as_str() {
|
|
298
|
+
let s = serde_json::to_string(&ConsultationStatus::WaitingHuman).unwrap();
|
|
299
|
+
assert_eq!(s, r#""WaitingHuman""#);
|
|
300
|
+
assert_ne!(s, r#""waiting_human""#);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#[test]
|
|
304
|
+
fn serde_round_trip_uses_variant_name() {
|
|
305
|
+
let val = ContainerOpStatus::Running;
|
|
306
|
+
let s = serde_json::to_string(&val).unwrap();
|
|
307
|
+
assert_eq!(s, r#""Running""#);
|
|
308
|
+
let back: ContainerOpStatus = serde_json::from_str(&s).unwrap();
|
|
309
|
+
assert_eq!(back, val);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#[test]
|
|
313
|
+
fn serde_rejects_as_str_value() {
|
|
314
|
+
// Deserializing the as_str() value fails — serde expects the
|
|
315
|
+
// PascalCase variant name.
|
|
316
|
+
assert!(serde_json::from_str::<ConsultationStatus>(r#""waiting_human""#).is_err());
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#[test]
|
|
320
|
+
fn serde_rejects_unknown_variant() {
|
|
321
|
+
assert!(serde_json::from_str::<ScriptLanguage>(r#""ruby""#).is_err());
|
|
322
|
+
assert!(serde_json::from_str::<GoalStatus>(r#""frozen""#).is_err());
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── PartialEq / Eq / Hash ──────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
#[test]
|
|
328
|
+
fn enum_equality_and_copy() {
|
|
329
|
+
let a = ContainerOpStatus::Running;
|
|
330
|
+
let b = a; // Copy
|
|
331
|
+
assert_eq!(a, b);
|
|
332
|
+
assert_ne!(a, ContainerOpStatus::Stopped);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//! External (third-party) MCP server connection configuration.
|
|
2
|
+
//!
|
|
3
|
+
//! These types describe how scepter connects to *out-of-tree* MCP servers
|
|
4
|
+
//! declared in the operator's `mcp_servers` TOML file. They are internal
|
|
5
|
+
//! config types — not exported to the TypeScript bindings — and are distinct
|
|
6
|
+
//! from the per-agent tool I/O structs under `mcp/`.
|
|
7
|
+
|
|
8
|
+
use serde::{Deserialize, Serialize};
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
11
|
+
#[serde(rename_all = "lowercase")]
|
|
12
|
+
pub enum TransportType {
|
|
13
|
+
Stdio,
|
|
14
|
+
Sse,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
18
|
+
pub struct McpServerConfig {
|
|
19
|
+
pub name: String,
|
|
20
|
+
pub transport: TransportType,
|
|
21
|
+
#[serde(default)]
|
|
22
|
+
pub command: Vec<String>,
|
|
23
|
+
#[serde(default)]
|
|
24
|
+
pub args: Vec<String>,
|
|
25
|
+
#[serde(default)]
|
|
26
|
+
pub endpoint: Option<String>,
|
|
27
|
+
#[serde(default)]
|
|
28
|
+
pub api_key: Option<String>,
|
|
29
|
+
#[serde(default)]
|
|
30
|
+
pub denylist: Vec<String>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
34
|
+
pub struct McpServersFile {
|
|
35
|
+
#[serde(default)]
|
|
36
|
+
pub mcp_servers: Vec<McpServerConfig>,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
40
|
+
pub struct ExternalToolInfo {
|
|
41
|
+
pub server_name: String,
|
|
42
|
+
pub tool_name: String,
|
|
43
|
+
pub description: String,
|
|
44
|
+
pub input_schema: serde_json::Value,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#[cfg(test)]
|
|
48
|
+
mod tests {
|
|
49
|
+
use super::*;
|
|
50
|
+
use anyhow::{Context, Result};
|
|
51
|
+
|
|
52
|
+
#[test]
|
|
53
|
+
fn parse_empty_file() -> Result<()> {
|
|
54
|
+
let raw = "";
|
|
55
|
+
let file: McpServersFile = toml::from_str(raw).context("test precondition")?;
|
|
56
|
+
assert!(file.mcp_servers.is_empty());
|
|
57
|
+
Ok(())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[test]
|
|
61
|
+
fn parse_single_stdio_server() -> Result<()> {
|
|
62
|
+
let raw = r#"
|
|
63
|
+
[[mcp_servers]]
|
|
64
|
+
name = "filesystem"
|
|
65
|
+
transport = "stdio"
|
|
66
|
+
command = ["npx", "-y", "@anthropic/mcp-server-filesystem"]
|
|
67
|
+
args = ["/home/user/projects"]
|
|
68
|
+
"#;
|
|
69
|
+
let file: McpServersFile = toml::from_str(raw).context("test precondition")?;
|
|
70
|
+
assert_eq!(file.mcp_servers.len(), 1);
|
|
71
|
+
let server = &file.mcp_servers[0];
|
|
72
|
+
assert_eq!(server.name, "filesystem");
|
|
73
|
+
assert_eq!(server.transport, TransportType::Stdio);
|
|
74
|
+
assert_eq!(
|
|
75
|
+
server.command,
|
|
76
|
+
vec!["npx", "-y", "@anthropic/mcp-server-filesystem"]
|
|
77
|
+
);
|
|
78
|
+
assert_eq!(server.args, vec!["/home/user/projects"]);
|
|
79
|
+
Ok(())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[test]
|
|
83
|
+
fn parse_multiple_servers() -> Result<()> {
|
|
84
|
+
let raw = r#"
|
|
85
|
+
[[mcp_servers]]
|
|
86
|
+
name = "filesystem"
|
|
87
|
+
transport = "stdio"
|
|
88
|
+
command = ["npx", "-y", "@anthropic/mcp-server-filesystem"]
|
|
89
|
+
|
|
90
|
+
[[mcp_servers]]
|
|
91
|
+
name = "postgres"
|
|
92
|
+
transport = "sse"
|
|
93
|
+
endpoint = "https://pg-mcp.example.com/mcp"
|
|
94
|
+
"#;
|
|
95
|
+
let file: McpServersFile = toml::from_str(raw).context("test precondition")?;
|
|
96
|
+
assert_eq!(file.mcp_servers.len(), 2);
|
|
97
|
+
assert_eq!(file.mcp_servers[0].transport, TransportType::Stdio);
|
|
98
|
+
assert_eq!(file.mcp_servers[1].transport, TransportType::Sse);
|
|
99
|
+
assert_eq!(
|
|
100
|
+
file.mcp_servers[1].endpoint.as_deref(),
|
|
101
|
+
Some("https://pg-mcp.example.com/mcp")
|
|
102
|
+
);
|
|
103
|
+
Ok(())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[test]
|
|
107
|
+
fn parse_server_with_denylist() -> Result<()> {
|
|
108
|
+
let raw = r#"
|
|
109
|
+
[[mcp_servers]]
|
|
110
|
+
name = "filesystem"
|
|
111
|
+
transport = "stdio"
|
|
112
|
+
command = ["npx", "mcp-server"]
|
|
113
|
+
denylist = ["rm_rf", "format_disk"]
|
|
114
|
+
"#;
|
|
115
|
+
let file: McpServersFile = toml::from_str(raw).context("test precondition")?;
|
|
116
|
+
assert_eq!(file.mcp_servers[0].denylist, vec!["rm_rf", "format_disk"]);
|
|
117
|
+
Ok(())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[test]
|
|
121
|
+
fn parse_commented_out_file() -> Result<()> {
|
|
122
|
+
let raw = r#"
|
|
123
|
+
# [[mcp_servers]]
|
|
124
|
+
# name = "filesystem"
|
|
125
|
+
# transport = "stdio"
|
|
126
|
+
# command = ["npx"]
|
|
127
|
+
"#;
|
|
128
|
+
let file: McpServersFile = toml::from_str(raw).context("test precondition")?;
|
|
129
|
+
assert!(file.mcp_servers.is_empty());
|
|
130
|
+
Ok(())
|
|
131
|
+
}
|
|
132
|
+
}
|