Skip to main content

smbcloud_ascapi_mcp/
lib.rs

1//! App Store Connect MCP contract.
2//!
3//! Defines the canonical tool set exposed by `asc --mcp`. Each name is
4//! supplied by the embedder through [`crate::ascapi_mcp_tools`], so the
5//! standalone binary and any host that re-exposes this contract share one
6//! implementation and one public surface. Same arrangement `xcrs` uses in
7//! `smbcloud-cli`.
8//!
9//! # Two rules this server is built around
10//!
11//! **stdout belongs to the protocol.** The stdio transport carries
12//! JSON-RPC on stdout, so a stray `println!` anywhere beneath a tool call
13//! corrupts the stream, and the failure surfaces as a parse error in the
14//! client a long way from its cause. Nothing in this path may write to
15//! stdout; diagnostics go to stderr.
16//!
17//! **Tool results are read by a model.** Private keys and raw certificate
18//! bodies never appear in a result; see
19//! [`smbcloud_ascapi_frontend`] for where that is enforced.
20//!
21//! # What is deliberately absent
22//!
23//! Certificate revocation. `asc certificates revoke` exists on the
24//! command line and is intentionally not a tool: revoking a distribution
25//! certificate invalidates every provisioning profile embedding it, for
26//! every teammate and every CI job, at once and irreversibly. That is not
27//! an action a language model should take from a prompt, and no
28//! confirmation string typed by the model rather than the human makes it
29//! safer. Deletes that are scoped and recreatable (a version, a
30//! localization, a screenshot) are exposed, annotated destructive.
31
32pub 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}