1pub mod requests;
33pub mod server;
34
35pub use requests::*;
36pub use server::{serve, AscapiMcpServer};
37
38#[macro_export]
39macro_rules! ascapi_mcp_tools {
40 (
41 $server:ty,
42 $app_list_name:literal,
43 $app_get_name:literal,
44 $app_update_name:literal,
45 $app_info_list_name:literal,
46 $build_list_name:literal,
47 $bundle_id_list_name:literal,
48 $bundle_id_create_name:literal,
49 $app_store_version_list_name:literal,
50 $app_store_version_get_name:literal,
51 $app_store_version_create_name:literal,
52 $app_store_version_delete_name:literal,
53 $app_store_version_set_build_name:literal,
54 $app_info_localization_list_name:literal,
55 $app_info_localization_create_name:literal,
56 $app_info_localization_update_name:literal,
57 $app_info_localization_delete_name:literal,
58 $app_store_version_localization_list_name:literal,
59 $app_store_version_localization_create_name:literal,
60 $app_store_version_localization_update_name:literal,
61 $app_store_version_localization_delete_name:literal,
62 $app_screenshot_set_list_name:literal,
63 $app_screenshot_set_create_name:literal,
64 $app_screenshot_set_delete_name:literal,
65 $app_screenshot_list_name:literal,
66 $app_screenshot_upload_name:literal,
67 $app_screenshot_delete_name:literal,
68 $certificate_list_name:literal,
69 $certificate_create_name:literal
70 ) => {
71 #[::rmcp::tool_router(router = ascapi_tool_router, vis = "pub(crate)")]
72 impl $server {
73 fn client() -> ::std::result::Result<::smbcloud_ascapi_core::Client, ::rmcp::model::ErrorData> {
74 let api_key = ::smbcloud_ascapi_frontend::api_key_from_env()
75 .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
76 Ok(::smbcloud_ascapi_core::Client::new(api_key))
77 }
78
79 #[::rmcp::tool(
80 name = $app_list_name,
81 title = "List apps",
82 annotations(title = "List apps", read_only_hint = true, idempotent_hint = true),
83 description = "Purpose: find the App Store Connect apps on this account, and translate a bundle identifier into the numeric app id every other tool wants. When to use vs siblings: call this first when you only know a bundle identifier; use app_get once you have an id and want one app's full attributes. Behavior: returns each app's id, name, bundle identifier, SKU, and primary locale, optionally filtered to one bundle identifier. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: returns apps only, not their versions, builds, or localizations, each of which has its own tool."
84 )]
85 async fn app_list(
86 &self,
87 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppListRequest>,
88 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
89 #[allow(unused_imports)]
90 use ::smbcloud_ascapi_aso::prelude::*;
91 #[allow(unused_imports)]
92 use ::smbcloud_ascapi_signing::prelude::*;
93 let client = Self::client()?;
94 let request = parameters.0;
95 let value = client
96 .list_apps(request.bundle_id.as_deref())
97 .await
98 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
99 Ok(::rmcp::model::CallToolResult::success(vec![
100 ::rmcp::model::ContentBlock::json(&value)?,
101 ]))
102 }
103
104 #[::rmcp::tool(
105 name = $app_get_name,
106 title = "Get one app",
107 annotations(title = "Get one app", read_only_hint = true, idempotent_hint = true),
108 description = "Purpose: fetch one app's full attributes by its App Store Connect id. When to use vs siblings: use app_list when you have a bundle identifier rather than an id, and app_info_list when you want the AppInfo records that hold localized names. Behavior: returns the single app resource with its attributes. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: attributes only; related resources such as versions and builds are fetched with their own tools."
109 )]
110 async fn app_get(
111 &self,
112 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppIdRequest>,
113 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
114 #[allow(unused_imports)]
115 use ::smbcloud_ascapi_aso::prelude::*;
116 #[allow(unused_imports)]
117 use ::smbcloud_ascapi_signing::prelude::*;
118 let client = Self::client()?;
119 let request = parameters.0;
120 let value = client
121 .get_app(&request.app_id)
122 .await
123 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
124 Ok(::rmcp::model::CallToolResult::success(vec![
125 ::rmcp::model::ContentBlock::json(&value)?,
126 ]))
127 }
128
129 #[::rmcp::tool(
130 name = $app_update_name,
131 title = "Update app attributes",
132 annotations(title = "Update app attributes", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
133 description = "Purpose: change an app's primary locale or content rights declaration. When to use vs siblings: this edits the app record itself; localized names and subtitles live on AppInfo localizations and are edited with app_info_localization_update. Behavior: sends only the fields provided, leaving the rest untouched, and returns the updated app. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: only these two attributes are editable through this endpoint; most app metadata belongs to a version or a localization instead."
134 )]
135 async fn app_update(
136 &self,
137 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppUpdateRequest>,
138 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
139 #[allow(unused_imports)]
140 use ::smbcloud_ascapi_aso::prelude::*;
141 #[allow(unused_imports)]
142 use ::smbcloud_ascapi_signing::prelude::*;
143 let client = Self::client()?;
144 let request = parameters.0;
145 let attributes = ::smbcloud_ascapi_aso::app::AppUpdateAttributes {
146 primary_locale: request.primary_locale,
147 content_rights_declaration: request.content_rights_declaration,
148 };
149 let value = client
150 .update_app(&request.app_id, attributes)
151 .await
152 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
153 Ok(::rmcp::model::CallToolResult::success(vec![
154 ::rmcp::model::ContentBlock::json(&value)?,
155 ]))
156 }
157
158 #[::rmcp::tool(
159 name = $app_info_list_name,
160 title = "List app infos",
161 annotations(title = "List app infos", read_only_hint = true, idempotent_hint = true),
162 description = "Purpose: list an app's AppInfo records, which are the containers for localized names, subtitles, and privacy policy URLs. When to use vs siblings: call this to get the app_info_id that app_info_localization_list and app_info_localization_create need. Behavior: returns each AppInfo with its state and id. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: an app typically has one editable AppInfo plus historical ones for shipped versions, and this returns all of them without saying which is editable."
163 )]
164 async fn app_info_list(
165 &self,
166 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppIdRequest>,
167 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
168 #[allow(unused_imports)]
169 use ::smbcloud_ascapi_aso::prelude::*;
170 #[allow(unused_imports)]
171 use ::smbcloud_ascapi_signing::prelude::*;
172 let client = Self::client()?;
173 let request = parameters.0;
174 let value = client
175 .list_app_infos(&request.app_id)
176 .await
177 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
178 Ok(::rmcp::model::CallToolResult::success(vec![
179 ::rmcp::model::ContentBlock::json(&value)?,
180 ]))
181 }
182
183 #[::rmcp::tool(
184 name = $build_list_name,
185 title = "List builds",
186 annotations(title = "List builds", read_only_hint = true, idempotent_hint = true),
187 description = "Purpose: list the builds uploaded for an app, so one can be attached to a version. When to use vs siblings: pair with app_store_version_set_build, which needs a build id from here. Behavior: returns each build's id, version string, upload date, and processing state, newest upload first, optionally narrowed by build number, marketing version, or processing state. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: a build that is still processing cannot be attached yet, and this reports that state rather than waiting for it."
188 )]
189 async fn build_list(
190 &self,
191 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BuildListRequest>,
192 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
193 #[allow(unused_imports)]
194 use ::smbcloud_ascapi_aso::prelude::*;
195 #[allow(unused_imports)]
196 use ::smbcloud_ascapi_signing::prelude::*;
197 let client = Self::client()?;
198 let request = parameters.0;
199 let filter = ::smbcloud_ascapi_aso::build::BuildFilter {
200 version: request.version,
201 pre_release_version: request.pre_release_version,
202 processing_state: request.processing_state,
203 };
204 let value = client
205 .list_builds(&request.app_id, &filter)
206 .await
207 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
208 Ok(::rmcp::model::CallToolResult::success(vec![
209 ::rmcp::model::ContentBlock::json(&value)?,
210 ]))
211 }
212
213 #[::rmcp::tool(
214 name = $bundle_id_list_name,
215 title = "List bundle IDs",
216 annotations(title = "List bundle IDs", read_only_hint = true, idempotent_hint = true),
217 description = "Purpose: list the bundle identifiers registered to the team, or check whether one exists before trying to register it. When to use vs siblings: call this before bundle_id_create, which fails on an identifier that already exists. Behavior: returns each bundle ID's id, identifier, name, and platform, optionally filtered to an exact identifier. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: registration state only; whether an app exists for the identifier is a separate question answered by app_list."
218 )]
219 async fn bundle_id_list(
220 &self,
221 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BundleIdListRequest>,
222 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
223 #[allow(unused_imports)]
224 use ::smbcloud_ascapi_aso::prelude::*;
225 #[allow(unused_imports)]
226 use ::smbcloud_ascapi_signing::prelude::*;
227 let client = Self::client()?;
228 let request = parameters.0;
229 let value = client
230 .list_bundle_ids(request.identifier.as_deref())
231 .await
232 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
233 Ok(::rmcp::model::CallToolResult::success(vec![
234 ::rmcp::model::ContentBlock::json(&value)?,
235 ]))
236 }
237
238 #[::rmcp::tool(
239 name = $bundle_id_create_name,
240 title = "Register a bundle ID",
241 annotations(title = "Register a bundle ID", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
242 description = "Purpose: register a new reverse-DNS bundle identifier with the developer account. When to use vs siblings: call bundle_id_list first, since registering an identifier that already exists fails. Behavior: registers the identifier under the given platform namespace and returns the created resource. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: identifiers cannot be renamed or deleted through this API once registered, so a typo here is permanent; visionOS apps register as ios because visionOS shares the iOS namespace."
243 )]
244 async fn bundle_id_create(
245 &self,
246 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BundleIdCreateRequest>,
247 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
248 #[allow(unused_imports)]
249 use ::smbcloud_ascapi_aso::prelude::*;
250 #[allow(unused_imports)]
251 use ::smbcloud_ascapi_signing::prelude::*;
252 let client = Self::client()?;
253 let request = parameters.0;
254 let platform = ::smbcloud_ascapi_frontend::bundle_id_platform_from_str(&request.platform)
255 .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
256 let attributes = ::smbcloud_ascapi_aso::bundle_id::BundleIdCreateAttributes {
257 identifier: request.identifier,
258 name: request.name,
259 platform,
260 };
261 let value = client
262 .create_bundle_id(attributes)
263 .await
264 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
265 Ok(::rmcp::model::CallToolResult::success(vec![
266 ::rmcp::model::ContentBlock::json(&value)?,
267 ]))
268 }
269
270 #[::rmcp::tool(
271 name = $app_store_version_list_name,
272 title = "List app store versions",
273 annotations(title = "List app store versions", read_only_hint = true, idempotent_hint = true),
274 description = "Purpose: list an app's per-platform version records, which are what the store actually reviews and releases. When to use vs siblings: use this to find the version id that the localization and build tools need. Behavior: returns each version's id, platform, version string, and app store state. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: versions are per platform, so one app can have several concurrent editable versions and this returns all of them."
275 )]
276 async fn app_store_version_list(
277 &self,
278 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppStoreVersionListRequest>,
279 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
280 #[allow(unused_imports)]
281 use ::smbcloud_ascapi_aso::prelude::*;
282 #[allow(unused_imports)]
283 use ::smbcloud_ascapi_signing::prelude::*;
284 let client = Self::client()?;
285 let request = parameters.0;
286 let platform = match request.platform.as_deref() {
287 Some(value) => Some(
288 ::smbcloud_ascapi_frontend::platform_from_str(value)
289 .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?,
290 ),
291 None => None,
292 };
293 let value = client
294 .list_app_store_versions(&request.app_id, platform)
295 .await
296 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
297 Ok(::rmcp::model::CallToolResult::success(vec![
298 ::rmcp::model::ContentBlock::json(&value)?,
299 ]))
300 }
301
302 #[::rmcp::tool(
303 name = $app_store_version_get_name,
304 title = "Get one app store version",
305 annotations(title = "Get one app store version", read_only_hint = true, idempotent_hint = true),
306 description = "Purpose: fetch one App Store Version's attributes by id. When to use vs siblings: use app_store_version_list when you have an app id rather than a version id. Behavior: returns the single version resource with its platform, version string, and release state. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: attributes only; its localizations and attached build are fetched with their own tools."
307 )]
308 async fn app_store_version_get(
309 &self,
310 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
311 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
312 #[allow(unused_imports)]
313 use ::smbcloud_ascapi_aso::prelude::*;
314 #[allow(unused_imports)]
315 use ::smbcloud_ascapi_signing::prelude::*;
316 let client = Self::client()?;
317 let request = parameters.0;
318 let value = client
319 .get_app_store_version(&request.id)
320 .await
321 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
322 Ok(::rmcp::model::CallToolResult::success(vec![
323 ::rmcp::model::ContentBlock::json(&value)?,
324 ]))
325 }
326
327 #[::rmcp::tool(
328 name = $app_store_version_create_name,
329 title = "Create an app store version",
330 annotations(title = "Create an app store version", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
331 description = "Purpose: create a new version record for an app on a given platform, which is how a new platform such as visionOS is added to an existing app. When to use vs siblings: call app_store_version_list first, because an app can hold only one editable version per platform at a time. Behavior: creates the version and returns it. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: creating a version does not submit anything; localizations, screenshots, and a build still have to be attached before review."
332 )]
333 async fn app_store_version_create(
334 &self,
335 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppStoreVersionCreateRequest>,
336 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
337 #[allow(unused_imports)]
338 use ::smbcloud_ascapi_aso::prelude::*;
339 #[allow(unused_imports)]
340 use ::smbcloud_ascapi_signing::prelude::*;
341 let client = Self::client()?;
342 let request = parameters.0;
343 let platform = ::smbcloud_ascapi_frontend::platform_from_str(&request.platform)
344 .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
345 let attributes = ::smbcloud_ascapi_aso::app_store_version::AppStoreVersionCreateAttributes {
346 platform,
347 version_string: request.version_string,
348 copyright: request.copyright,
349 };
350 let value = client
351 .create_app_store_version(&request.app_id, attributes)
352 .await
353 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
354 Ok(::rmcp::model::CallToolResult::success(vec![
355 ::rmcp::model::ContentBlock::json(&value)?,
356 ]))
357 }
358
359 #[::rmcp::tool(
360 name = $app_store_version_delete_name,
361 title = "Delete an app store version",
362 annotations(title = "Delete an app store version", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
363 description = "Purpose: delete an editable App Store Version record. When to use vs siblings: use this to discard a version created by mistake, typically after app_store_version_list shows a duplicate. Behavior: deletes the version and everything attached to it, including its localizations and screenshots, then returns the deleted id. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: destructive and not undoable, though the version can be recreated; a version already submitted or released cannot be deleted and Apple rejects the attempt."
364 )]
365 async fn app_store_version_delete(
366 &self,
367 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
368 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
369 #[allow(unused_imports)]
370 use ::smbcloud_ascapi_aso::prelude::*;
371 #[allow(unused_imports)]
372 use ::smbcloud_ascapi_signing::prelude::*;
373 let client = Self::client()?;
374 let request = parameters.0;
375 client
376 .delete_app_store_version(&request.id)
377 .await
378 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
379 Ok(::rmcp::model::CallToolResult::success(vec![
380 ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
381 ]))
382 }
383
384 #[::rmcp::tool(
385 name = $app_store_version_set_build_name,
386 title = "Attach a build to a version",
387 annotations(title = "Attach a build to a version", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
388 description = "Purpose: attach an uploaded build to an App Store Version, which a version needs before it can be submitted. When to use vs siblings: get the build id from build_list and the version id from app_store_version_list. Behavior: points the version's build relationship at the given build, replacing whatever was attached. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: the build must have finished processing, and its platform must match the version's; Apple rejects the pairing otherwise."
389 )]
390 async fn app_store_version_set_build(
391 &self,
392 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::SetBuildRequest>,
393 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
394 #[allow(unused_imports)]
395 use ::smbcloud_ascapi_aso::prelude::*;
396 #[allow(unused_imports)]
397 use ::smbcloud_ascapi_signing::prelude::*;
398 let client = Self::client()?;
399 let request = parameters.0;
400 client
401 .set_app_store_version_build(&request.version_id, &request.build_id)
402 .await
403 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
404 Ok(::rmcp::model::CallToolResult::success(vec![
405 ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "versionId": request.version_id, "buildId": request.build_id }))?,
406 ]))
407 }
408
409 #[::rmcp::tool(
410 name = $app_info_localization_list_name,
411 title = "List app info localizations",
412 annotations(title = "List app info localizations", read_only_hint = true, idempotent_hint = true),
413 description = "Purpose: list the localized names and subtitles attached to an AppInfo. When to use vs siblings: this covers name, subtitle, and privacy policy URL; description, keywords, and release notes live on version localizations instead. Behavior: returns each localization's id, locale, name, and subtitle. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: only the editable AppInfo accepts changes, and this does not distinguish editable from historical records."
414 )]
415 async fn app_info_localization_list(
416 &self,
417 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationListRequest>,
418 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
419 #[allow(unused_imports)]
420 use ::smbcloud_ascapi_aso::prelude::*;
421 #[allow(unused_imports)]
422 use ::smbcloud_ascapi_signing::prelude::*;
423 let client = Self::client()?;
424 let request = parameters.0;
425 let value = client
426 .list_app_info_localizations(&request.app_info_id)
427 .await
428 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
429 Ok(::rmcp::model::CallToolResult::success(vec![
430 ::rmcp::model::ContentBlock::json(&value)?,
431 ]))
432 }
433
434 #[::rmcp::tool(
435 name = $app_info_localization_create_name,
436 title = "Add an app info localization",
437 annotations(title = "Add an app info localization", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
438 description = "Purpose: add a localized app name and subtitle for a new locale. When to use vs siblings: use app_info_localization_update when the locale already exists, since creating a duplicate fails. Behavior: creates the localization under the AppInfo and returns it. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: the subtitle is capped at 30 characters and the name at 30 by App Store Connect, which rejects longer values rather than truncating them."
439 )]
440 async fn app_info_localization_create(
441 &self,
442 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationCreateRequest>,
443 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
444 #[allow(unused_imports)]
445 use ::smbcloud_ascapi_aso::prelude::*;
446 #[allow(unused_imports)]
447 use ::smbcloud_ascapi_signing::prelude::*;
448 let client = Self::client()?;
449 let request = parameters.0;
450 let attributes = ::smbcloud_ascapi_aso::app_info_localization::AppInfoLocalizationCreateAttributes {
451 locale: request.locale,
452 name: request.name,
453 subtitle: request.subtitle,
454 privacy_policy_url: request.privacy_policy_url,
455 };
456 let value = client
457 .create_app_info_localization(&request.app_info_id, attributes)
458 .await
459 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
460 Ok(::rmcp::model::CallToolResult::success(vec![
461 ::rmcp::model::ContentBlock::json(&value)?,
462 ]))
463 }
464
465 #[::rmcp::tool(
466 name = $app_info_localization_update_name,
467 title = "Edit an app info localization",
468 annotations(title = "Edit an app info localization", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
469 description = "Purpose: change the localized name, subtitle, or privacy policy URL for a locale that already exists. When to use vs siblings: use app_info_localization_create for a locale that has no record yet. Behavior: sends only the fields provided and leaves the others untouched, then returns the updated localization. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: the locale itself cannot be changed; delete and recreate to move copy to a different locale."
470 )]
471 async fn app_info_localization_update(
472 &self,
473 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationUpdateRequest>,
474 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
475 #[allow(unused_imports)]
476 use ::smbcloud_ascapi_aso::prelude::*;
477 #[allow(unused_imports)]
478 use ::smbcloud_ascapi_signing::prelude::*;
479 let client = Self::client()?;
480 let request = parameters.0;
481 let attributes = ::smbcloud_ascapi_aso::app_info_localization::AppInfoLocalizationUpdateAttributes {
482 name: request.name,
483 subtitle: request.subtitle,
484 privacy_policy_url: request.privacy_policy_url,
485 };
486 let value = client
487 .update_app_info_localization(&request.id, attributes)
488 .await
489 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
490 Ok(::rmcp::model::CallToolResult::success(vec![
491 ::rmcp::model::ContentBlock::json(&value)?,
492 ]))
493 }
494
495 #[::rmcp::tool(
496 name = $app_info_localization_delete_name,
497 title = "Delete an app info localization",
498 annotations(title = "Delete an app info localization", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
499 description = "Purpose: remove a locale's app name and subtitle. When to use vs siblings: use this to drop a language from the product page; use app_info_localization_update to change copy rather than remove it. Behavior: deletes the localization and returns the deleted id. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: destructive but recreatable with app_info_localization_create; the app's primary locale cannot be deleted and Apple rejects the attempt."
500 )]
501 async fn app_info_localization_delete(
502 &self,
503 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
504 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
505 #[allow(unused_imports)]
506 use ::smbcloud_ascapi_aso::prelude::*;
507 #[allow(unused_imports)]
508 use ::smbcloud_ascapi_signing::prelude::*;
509 let client = Self::client()?;
510 let request = parameters.0;
511 client
512 .delete_app_info_localization(&request.id)
513 .await
514 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
515 Ok(::rmcp::model::CallToolResult::success(vec![
516 ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
517 ]))
518 }
519
520 #[::rmcp::tool(
521 name = $app_store_version_localization_list_name,
522 title = "List version localizations",
523 annotations(title = "List version localizations", read_only_hint = true, idempotent_hint = true),
524 description = "Purpose: list the per-locale description, keywords, promotional text, and release notes for a version. When to use vs siblings: this is the version-scoped copy; the app name and subtitle live on AppInfo localizations instead. Behavior: returns each localization's id, locale, and copy fields. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: screenshots hang off these localizations but are listed separately with app_screenshot_set_list."
525 )]
526 async fn app_store_version_localization_list(
527 &self,
528 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationListRequest>,
529 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
530 #[allow(unused_imports)]
531 use ::smbcloud_ascapi_aso::prelude::*;
532 #[allow(unused_imports)]
533 use ::smbcloud_ascapi_signing::prelude::*;
534 let client = Self::client()?;
535 let request = parameters.0;
536 let value = client
537 .list_app_store_version_localizations(&request.version_id)
538 .await
539 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
540 Ok(::rmcp::model::CallToolResult::success(vec![
541 ::rmcp::model::ContentBlock::json(&value)?,
542 ]))
543 }
544
545 #[::rmcp::tool(
546 name = $app_store_version_localization_create_name,
547 title = "Add a version localization",
548 annotations(title = "Add a version localization", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
549 description = "Purpose: add the description, keywords, and release notes for a locale on a specific version. When to use vs siblings: use the update tool when the locale already exists on this version. Behavior: creates the localization with whichever fields are provided and returns it. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: App Store Connect enforces field limits, notably 4000 characters of description, 100 for keywords, and 170 for promotional text, and rejects longer values outright."
550 )]
551 async fn app_store_version_localization_create(
552 &self,
553 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationCreateRequest>,
554 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
555 #[allow(unused_imports)]
556 use ::smbcloud_ascapi_aso::prelude::*;
557 #[allow(unused_imports)]
558 use ::smbcloud_ascapi_signing::prelude::*;
559 let client = Self::client()?;
560 let request = parameters.0;
561 let fields = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationFields {
562 description: request.description,
563 keywords: request.keywords,
564 marketing_url: request.marketing_url,
565 promotional_text: request.promotional_text,
566 support_url: request.support_url,
567 whats_new: request.whats_new,
568 };
569 let attributes = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationCreateAttributes {
570 locale: request.locale,
571 fields,
572 };
573 let value = client
574 .create_app_store_version_localization(&request.version_id, attributes)
575 .await
576 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
577 Ok(::rmcp::model::CallToolResult::success(vec![
578 ::rmcp::model::ContentBlock::json(&value)?,
579 ]))
580 }
581
582 #[::rmcp::tool(
583 name = $app_store_version_localization_update_name,
584 title = "Edit a version localization",
585 annotations(title = "Edit a version localization", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
586 description = "Purpose: change the description, keywords, promotional text, or release notes for an existing locale on a version. When to use vs siblings: promotional text is the one field editable without shipping a new build, which makes this the tool for timely copy changes. Behavior: sends only the fields provided and leaves the rest untouched. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: which fields are editable depends on the version's review state, and Apple rejects edits to a version under review."
587 )]
588 async fn app_store_version_localization_update(
589 &self,
590 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationUpdateRequest>,
591 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
592 #[allow(unused_imports)]
593 use ::smbcloud_ascapi_aso::prelude::*;
594 #[allow(unused_imports)]
595 use ::smbcloud_ascapi_signing::prelude::*;
596 let client = Self::client()?;
597 let request = parameters.0;
598 let fields = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationFields {
599 description: request.description,
600 keywords: request.keywords,
601 marketing_url: request.marketing_url,
602 promotional_text: request.promotional_text,
603 support_url: request.support_url,
604 whats_new: request.whats_new,
605 };
606 let value = client
607 .update_app_store_version_localization(&request.id, fields)
608 .await
609 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
610 Ok(::rmcp::model::CallToolResult::success(vec![
611 ::rmcp::model::ContentBlock::json(&value)?,
612 ]))
613 }
614
615 #[::rmcp::tool(
616 name = $app_store_version_localization_delete_name,
617 title = "Delete a version localization",
618 annotations(title = "Delete a version localization", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
619 description = "Purpose: remove a locale's copy from a version. When to use vs siblings: use the update tool to change copy; use this only to drop a language from this version entirely. Behavior: deletes the localization along with its screenshot sets and returns the deleted id. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: destructive and takes the locale's screenshots with it, though everything can be recreated; the primary locale cannot be removed."
620 )]
621 async fn app_store_version_localization_delete(
622 &self,
623 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
624 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
625 #[allow(unused_imports)]
626 use ::smbcloud_ascapi_aso::prelude::*;
627 #[allow(unused_imports)]
628 use ::smbcloud_ascapi_signing::prelude::*;
629 let client = Self::client()?;
630 let request = parameters.0;
631 client
632 .delete_app_store_version_localization(&request.id)
633 .await
634 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
635 Ok(::rmcp::model::CallToolResult::success(vec![
636 ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
637 ]))
638 }
639
640 #[::rmcp::tool(
641 name = $app_screenshot_set_list_name,
642 title = "List screenshot sets",
643 annotations(title = "List screenshot sets", read_only_hint = true, idempotent_hint = true),
644 description = "Purpose: list the per-device-class screenshot buckets on a version localization. When to use vs siblings: each set holds the images for one display type, so call this to find the set id that app_screenshot_list and app_screenshot_upload need. Behavior: returns each set's id and display type. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: reports the sets, not how many images each holds or whether the set satisfies Apple's per-device requirements."
645 )]
646 async fn app_screenshot_set_list(
647 &self,
648 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotSetListRequest>,
649 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
650 #[allow(unused_imports)]
651 use ::smbcloud_ascapi_aso::prelude::*;
652 #[allow(unused_imports)]
653 use ::smbcloud_ascapi_signing::prelude::*;
654 let client = Self::client()?;
655 let request = parameters.0;
656 let value = client
657 .list_app_screenshot_sets(&request.localization_id)
658 .await
659 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
660 Ok(::rmcp::model::CallToolResult::success(vec![
661 ::rmcp::model::ContentBlock::json(&value)?,
662 ]))
663 }
664
665 #[::rmcp::tool(
666 name = $app_screenshot_set_create_name,
667 title = "Create a screenshot set",
668 annotations(title = "Create a screenshot set", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
669 description = "Purpose: create the bucket that holds screenshots for one device class on a version localization. When to use vs siblings: call app_screenshot_set_list first, since a localization holds at most one set per display type. Behavior: creates the set for the given display type and returns it, ready for app_screenshot_upload. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: the display type must be one App Store Connect recognizes, and image dimensions are validated later at upload rather than here."
670 )]
671 async fn app_screenshot_set_create(
672 &self,
673 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotSetCreateRequest>,
674 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
675 #[allow(unused_imports)]
676 use ::smbcloud_ascapi_aso::prelude::*;
677 #[allow(unused_imports)]
678 use ::smbcloud_ascapi_signing::prelude::*;
679 let client = Self::client()?;
680 let request = parameters.0;
681 let display_type = ::smbcloud_ascapi_frontend::display_type_from_str(&request.display_type)
682 .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
683 let attributes = ::smbcloud_ascapi_aso::app_screenshot_set::AppScreenshotSetCreateAttributes {
684 screenshot_display_type: display_type,
685 };
686 let value = client
687 .create_app_screenshot_set(&request.localization_id, attributes)
688 .await
689 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
690 Ok(::rmcp::model::CallToolResult::success(vec![
691 ::rmcp::model::ContentBlock::json(&value)?,
692 ]))
693 }
694
695 #[::rmcp::tool(
696 name = $app_screenshot_set_delete_name,
697 title = "Delete a screenshot set",
698 annotations(title = "Delete a screenshot set", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
699 description = "Purpose: remove a device class's screenshot bucket and every image in it. When to use vs siblings: use app_screenshot_delete to remove a single image instead of the whole set. Behavior: deletes the set and its screenshots, then returns the deleted id. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: destructive and takes every image with it, though the set and its images can be recreated by uploading again."
700 )]
701 async fn app_screenshot_set_delete(
702 &self,
703 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
704 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
705 #[allow(unused_imports)]
706 use ::smbcloud_ascapi_aso::prelude::*;
707 #[allow(unused_imports)]
708 use ::smbcloud_ascapi_signing::prelude::*;
709 let client = Self::client()?;
710 let request = parameters.0;
711 client
712 .delete_app_screenshot_set(&request.id)
713 .await
714 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
715 Ok(::rmcp::model::CallToolResult::success(vec![
716 ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
717 ]))
718 }
719
720 #[::rmcp::tool(
721 name = $app_screenshot_list_name,
722 title = "List screenshots",
723 annotations(title = "List screenshots", read_only_hint = true, idempotent_hint = true),
724 description = "Purpose: list the images in a screenshot set, with their upload and processing state. When to use vs siblings: call this after app_screenshot_upload to confirm an image finished processing, since upload returns before Apple has validated the file. Behavior: returns each screenshot's id, file name, size, and asset delivery state. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: an image that failed Apple's dimension checks appears here with an error state rather than being absent, so the state is worth reading."
725 )]
726 async fn app_screenshot_list(
727 &self,
728 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotListRequest>,
729 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
730 #[allow(unused_imports)]
731 use ::smbcloud_ascapi_aso::prelude::*;
732 #[allow(unused_imports)]
733 use ::smbcloud_ascapi_signing::prelude::*;
734 let client = Self::client()?;
735 let request = parameters.0;
736 let value = client
737 .list_app_screenshots(&request.screenshot_set_id)
738 .await
739 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
740 Ok(::rmcp::model::CallToolResult::success(vec![
741 ::rmcp::model::ContentBlock::json(&value)?,
742 ]))
743 }
744
745 #[::rmcp::tool(
746 name = $app_screenshot_upload_name,
747 title = "Upload a screenshot",
748 annotations(title = "Upload a screenshot", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
749 description = "Purpose: upload one image into a screenshot set, running the whole three-step reservation, upload, and commit dance in a single call. When to use vs siblings: create the set with app_screenshot_set_create first, then verify with app_screenshot_list, since Apple validates asynchronously. Behavior: reads the file from this machine, reserves the asset, PUTs the bytes to the pre-signed URL, and commits it with its checksum. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: an unreadable path fails before any network call; a wrong image size is accepted here and rejected later, surfacing in app_screenshot_list. Limitations: the path is read by this process, so it must be reachable from wherever the server runs, not from the client's machine."
750 )]
751 async fn app_screenshot_upload(
752 &self,
753 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotUploadRequest>,
754 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
755 #[allow(unused_imports)]
756 use ::smbcloud_ascapi_aso::prelude::*;
757 #[allow(unused_imports)]
758 use ::smbcloud_ascapi_signing::prelude::*;
759 let client = Self::client()?;
760 let request = parameters.0;
761 let path = ::std::path::PathBuf::from(&request.file_path);
762 let bytes = ::std::fs::read(&path).map_err(|error| {
763 ::rmcp::model::ErrorData::invalid_request(
764 format!("reading {}: {error}", path.display()),
765 None,
766 )
767 })?;
768 let file_name = path
769 .file_name()
770 .map(|name| name.to_string_lossy().into_owned())
771 .ok_or_else(|| {
772 ::rmcp::model::ErrorData::invalid_request(
773 format!("{} has no file name", path.display()),
774 None,
775 )
776 })?;
777 let value = client
778 .upload_app_screenshot(&request.screenshot_set_id, file_name, bytes)
779 .await
780 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
781 Ok(::rmcp::model::CallToolResult::success(vec![
782 ::rmcp::model::ContentBlock::json(&value)?,
783 ]))
784 }
785
786 #[::rmcp::tool(
787 name = $app_screenshot_delete_name,
788 title = "Delete a screenshot",
789 annotations(title = "Delete a screenshot", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
790 description = "Purpose: remove one uploaded screenshot. When to use vs siblings: use app_screenshot_set_delete to clear an entire device class at once. Behavior: deletes the image and returns the deleted id. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: destructive but trivially recoverable by uploading the file again, which makes it the safest of the delete tools."
791 )]
792 async fn app_screenshot_delete(
793 &self,
794 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
795 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
796 #[allow(unused_imports)]
797 use ::smbcloud_ascapi_aso::prelude::*;
798 #[allow(unused_imports)]
799 use ::smbcloud_ascapi_signing::prelude::*;
800 let client = Self::client()?;
801 let request = parameters.0;
802 client
803 .delete_app_screenshot(&request.id)
804 .await
805 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
806 Ok(::rmcp::model::CallToolResult::success(vec![
807 ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
808 ]))
809 }
810
811 #[::rmcp::tool(
812 name = $certificate_list_name,
813 title = "List signing certificates",
814 annotations(title = "List signing certificates", read_only_hint = true, idempotent_hint = true),
815 description = "Purpose: report every signing certificate the team holds, with the expiry of each, so an agent can tell what is usable before a build is attempted. When to use vs siblings: call this first to see whether a valid certificate already exists; use certificate_create only when none of the required type exists, or the existing one has expired. Behavior: returns id, name, type, serial number, expiry, and a computed expired flag, soonest expiry first. The flag is computed here because App Store Connect offers no filter for it and returns expired certificates alongside valid ones. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: certificate bodies are omitted deliberately, and this reports certificates only, not provisioning profiles, so a valid certificate here does not by itself mean a build will sign."
816 )]
817 async fn certificate_list(
818 &self,
819 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::CertificateListRequest>,
820 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
821 #[allow(unused_imports)]
822 use ::smbcloud_ascapi_aso::prelude::*;
823 #[allow(unused_imports)]
824 use ::smbcloud_ascapi_signing::prelude::*;
825 let client = Self::client()?;
826 let request = parameters.0;
827 let filter = match request.certificate_type.as_deref() {
828 Some(value) => Some(
829 ::smbcloud_ascapi_frontend::certificate_type_from_str(value)
830 .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?,
831 ),
832 None => None,
833 };
834 let mut certificates = client.list_certificates(filter).await.map_err(|error| {
835 ::rmcp::model::ErrorData::internal_error(error.to_string(), None)
836 })?;
837 certificates.sort_by(|a, b| {
838 a.attributes.expiration_date.cmp(&b.attributes.expiration_date)
839 });
840 let now = ::smbcloud_ascapi_frontend::now_iso8601();
841 let summaries: Vec<_> = certificates
842 .iter()
843 .map(|certificate| ::smbcloud_ascapi_frontend::CertificateSummary::from_resource(certificate, &now))
844 .collect();
845 Ok(::rmcp::model::CallToolResult::success(vec![
846 ::rmcp::model::ContentBlock::json(&summaries)?,
847 ]))
848 }
849
850 #[::rmcp::tool(
851 name = $certificate_create_name,
852 title = "Issue a signing certificate",
853 annotations(title = "Issue a signing certificate", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
854 description = "Purpose: issue a new Apple signing certificate end to end, generating the key pair locally and writing both halves to disk, so a machine can sign without anyone visiting the developer portal. When to use vs siblings: call certificate_list first and use this only when no valid certificate of the needed type exists; an expired certificate cannot be renewed, so replacing one also means calling this. Behavior: generates an RSA 2048 key pair, the only kind Apple accepts, writes the private key with owner-only permissions, sends a signing request, then writes the issued certificate beside the key and returns both paths. The key is written before the request is sent, so a failure mid-flight leaves an unused key rather than a certificate whose key was never saved. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: Apple caps how many certificates of each type a team may hold and returns an error at the cap, which is resolved by revoking one from the command line, never from here. Limitations: the response carries paths, never key material; Apple keeps no copy of the private key, so losing the file makes the certificate permanently useless."
855 )]
856 async fn certificate_create(
857 &self,
858 parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::CertificateCreateRequest>,
859 ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
860 #[allow(unused_imports)]
861 use ::smbcloud_ascapi_aso::prelude::*;
862 #[allow(unused_imports)]
863 use ::smbcloud_ascapi_signing::prelude::*;
864 let client = Self::client()?;
865 let request = parameters.0;
866 let certificate_type = ::smbcloud_ascapi_frontend::certificate_type_from_str(&request.certificate_type)
867 .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
868 let common_name = request
869 .common_name
870 .unwrap_or_else(|| "smbcloud-ascapi".to_string());
871 let out_dir = ::std::path::PathBuf::from(&request.out_dir);
872 let outcome = ::smbcloud_ascapi_frontend::issue_certificate(
873 &client,
874 certificate_type,
875 &common_name,
876 &out_dir,
877 )
878 .await
879 .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
880 Ok(::rmcp::model::CallToolResult::success(vec![
881 ::rmcp::model::ContentBlock::json(&outcome)?,
882 ]))
883 }
884 }
885 };
886}