22
schemars::JsonSchema,
23
serde::Deserialize,
24
smbcloud_auth::me::me,
25
+ smbcloud_model::project::ProjectCreate,
26
smbcloud_network::environment::Environment,
27
smbcloud_networking_project::{
28
+ crud_project_create::create_project, crud_project_delete::delete_project,
29
crud_project_deployment_read::get_deployments, crud_project_read::get_project,
28
- crud_project_read::get_projects,
30
+ crud_project_read::get_projects, crud_project_update::update_project,
31
},
32
};
33
72
project_id: i32,
73
}
74
75
+#[derive(Debug, Deserialize, JsonSchema)]
76
+struct ProjectCreateArgs {
77
+ /// Name for the new project.
78
+ name: String,
79
+ /// Optional description for the new project.
80
+ #[serde(default)]
81
+ description: String,
82
+}
83
+
84
+#[derive(Debug, Deserialize, JsonSchema)]
85
+struct ProjectUpdateArgs {
86
+ /// The project ID to update.
87
+ id: String,
88
+ /// The new description.
89
+ description: String,
90
+}
91
+
92
+#[derive(Debug, Deserialize, JsonSchema)]
93
+struct ProjectDeleteArgs {
94
+ /// The project ID to delete.
95
+ id: String,
96
+}
97
+
98
#[tool_router]
99
impl SmbMcpServer {
100
#[tool(description = "Get the authenticated smbCloud user's account info. \
139
.map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
140
json_result(&deployments)
141
}
142
+
143
+ #[tool(
144
+ description = "Create a new smbCloud project with a name and optional description. \
145
+ Returns the created project as JSON."
146
+ )]
147
+ async fn project_create(
148
+ &self,
149
+ Parameters(args): Parameters<ProjectCreateArgs>,
150
+ ) -> Result<CallToolResult, ErrorData> {
151
+ let token = self.access_token()?;
152
+ let payload = ProjectCreate {
153
+ name: args.name,
154
+ description: args.description,
155
+ };
156
+ let project = create_project(self.environment, client(), token, payload)
157
+ .await
158
+ .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
159
+ json_result(&project)
160
+ }
161
+
162
+ #[tool(
163
+ description = "Update a project's description by ID, preserving its runner. \
164
+ Returns the updated project as JSON."
165
+ )]
166
+ async fn project_update(
167
+ &self,
168
+ Parameters(args): Parameters<ProjectUpdateArgs>,
169
+ ) -> Result<CallToolResult, ErrorData> {
170
+ let token = self.access_token()?;
171
+ // `update_project` requires the runner; fetch the current project so the
172
+ // description-only update doesn't clobber it.
173
+ let current = get_project(self.environment, client(), token.clone(), args.id.clone())
174
+ .await
175
+ .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
176
+ let project = update_project(
177
+ self.environment,
178
+ client(),
179
+ token,
180
+ args.id,
181
+ &args.description,
182
+ current.runner,
183
+ )
184
+ .await
185
+ .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
186
+ json_result(&project)
187
+ }
188
+
189
+ #[tool(
190
+ description = "Delete a project by ID. This is destructive and irreversible — the \
191
+ project and its deploy configuration are removed."
192
+ )]
193
+ async fn project_delete(
194
+ &self,
195
+ Parameters(args): Parameters<ProjectDeleteArgs>,
196
+ ) -> Result<CallToolResult, ErrorData> {
197
+ let token = self.access_token()?;
198
+ delete_project(self.environment, client(), token, args.id)
199
+ .await
200
+ .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
201
+ Ok(CallToolResult::success(vec![ContentBlock::text(
202
+ "Project deleted.",
203
+ )]))
204
+ }
205
}
206
207
#[tool_handler]