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_store_version_build_get_name:literal,
55        $app_info_localization_list_name:literal,
56        $app_info_localization_create_name:literal,
57        $app_info_localization_update_name:literal,
58        $app_info_localization_delete_name:literal,
59        $app_store_version_localization_list_name:literal,
60        $app_store_version_localization_create_name:literal,
61        $app_store_version_localization_update_name:literal,
62        $app_store_version_localization_delete_name:literal,
63        $app_screenshot_set_list_name:literal,
64        $app_screenshot_set_create_name:literal,
65        $app_screenshot_set_delete_name:literal,
66        $app_screenshot_list_name:literal,
67        $app_screenshot_upload_name:literal,
68        $app_screenshot_delete_name:literal,
69        $certificate_list_name:literal,
70        $certificate_create_name:literal,
71        $beta_group_list_name:literal,
72        $beta_app_review_detail_get_name:literal,
73        $play_listing_list_name:literal,
74        $play_images_list_name:literal,
75        $play_track_get_name:literal
76    ) => {
77        #[::rmcp::tool_router(router = ascapi_tool_router, vis = "pub(crate)")]
78        impl $server {
79            fn client() -> ::std::result::Result<::smbcloud_ascapi_core::Client, ::rmcp::model::ErrorData> {
80                let api_key = ::smbcloud_ascapi_frontend::api_key_from_env()
81                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
82                Ok(::smbcloud_ascapi_core::Client::new(api_key))
83            }
84
85            #[::rmcp::tool(
86                name = $app_list_name,
87                title = "List apps",
88                annotations(title = "List apps", read_only_hint = true, idempotent_hint = true),
89                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."
90            )]
91            async fn app_list(
92                &self,
93                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppListRequest>,
94            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
95                #[allow(unused_imports)]
96                use ::smbcloud_ascapi_aso::prelude::*;
97                #[allow(unused_imports)]
98                use ::smbcloud_ascapi_signing::prelude::*;
99                let client = Self::client()?;
100                let request = parameters.0;
101                let value = client
102                    .list_apps(request.bundle_id.as_deref())
103                    .await
104                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
105                Ok(::rmcp::model::CallToolResult::success(vec![
106                    ::rmcp::model::ContentBlock::json(&value)?,
107                ]))
108            }
109
110            #[::rmcp::tool(
111                name = $app_get_name,
112                title = "Get one app",
113                annotations(title = "Get one app", read_only_hint = true, idempotent_hint = true),
114                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."
115            )]
116            async fn app_get(
117                &self,
118                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppIdRequest>,
119            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
120                #[allow(unused_imports)]
121                use ::smbcloud_ascapi_aso::prelude::*;
122                #[allow(unused_imports)]
123                use ::smbcloud_ascapi_signing::prelude::*;
124                let client = Self::client()?;
125                let request = parameters.0;
126                let value = client
127                    .get_app(&request.app_id)
128                    .await
129                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
130                Ok(::rmcp::model::CallToolResult::success(vec![
131                    ::rmcp::model::ContentBlock::json(&value)?,
132                ]))
133            }
134
135            #[::rmcp::tool(
136                name = $app_update_name,
137                title = "Update app attributes",
138                annotations(title = "Update app attributes", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
139                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."
140            )]
141            async fn app_update(
142                &self,
143                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppUpdateRequest>,
144            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
145                #[allow(unused_imports)]
146                use ::smbcloud_ascapi_aso::prelude::*;
147                #[allow(unused_imports)]
148                use ::smbcloud_ascapi_signing::prelude::*;
149                let client = Self::client()?;
150                let request = parameters.0;
151                let attributes = ::smbcloud_ascapi_aso::app::AppUpdateAttributes {
152                    primary_locale: request.primary_locale,
153                    content_rights_declaration: request.content_rights_declaration,
154                };
155                let value = client
156                    .update_app(&request.app_id, attributes)
157                    .await
158                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
159                Ok(::rmcp::model::CallToolResult::success(vec![
160                    ::rmcp::model::ContentBlock::json(&value)?,
161                ]))
162            }
163
164            #[::rmcp::tool(
165                name = $app_info_list_name,
166                title = "List app infos",
167                annotations(title = "List app infos", read_only_hint = true, idempotent_hint = true),
168                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."
169            )]
170            async fn app_info_list(
171                &self,
172                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppIdRequest>,
173            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
174                #[allow(unused_imports)]
175                use ::smbcloud_ascapi_aso::prelude::*;
176                #[allow(unused_imports)]
177                use ::smbcloud_ascapi_signing::prelude::*;
178                let client = Self::client()?;
179                let request = parameters.0;
180                let value = client
181                    .list_app_infos(&request.app_id)
182                    .await
183                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
184                Ok(::rmcp::model::CallToolResult::success(vec![
185                    ::rmcp::model::ContentBlock::json(&value)?,
186                ]))
187            }
188
189            #[::rmcp::tool(
190                name = $build_list_name,
191                title = "List builds",
192                annotations(title = "List builds", read_only_hint = true, idempotent_hint = true),
193                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."
194            )]
195            async fn build_list(
196                &self,
197                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BuildListRequest>,
198            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
199                #[allow(unused_imports)]
200                use ::smbcloud_ascapi_aso::prelude::*;
201                #[allow(unused_imports)]
202                use ::smbcloud_ascapi_signing::prelude::*;
203                let client = Self::client()?;
204                let request = parameters.0;
205                let filter = ::smbcloud_ascapi_aso::build::BuildFilter {
206                    version: request.version,
207                    pre_release_version: request.pre_release_version,
208                    processing_state: request.processing_state,
209                };
210                let value = client
211                    .list_builds(&request.app_id, &filter)
212                    .await
213                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
214                Ok(::rmcp::model::CallToolResult::success(vec![
215                    ::rmcp::model::ContentBlock::json(&value)?,
216                ]))
217            }
218
219            #[::rmcp::tool(
220                name = $bundle_id_list_name,
221                title = "List bundle IDs",
222                annotations(title = "List bundle IDs", read_only_hint = true, idempotent_hint = true),
223                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."
224            )]
225            async fn bundle_id_list(
226                &self,
227                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BundleIdListRequest>,
228            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
229                #[allow(unused_imports)]
230                use ::smbcloud_ascapi_aso::prelude::*;
231                #[allow(unused_imports)]
232                use ::smbcloud_ascapi_signing::prelude::*;
233                let client = Self::client()?;
234                let request = parameters.0;
235                let value = client
236                    .list_bundle_ids(request.identifier.as_deref())
237                    .await
238                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
239                Ok(::rmcp::model::CallToolResult::success(vec![
240                    ::rmcp::model::ContentBlock::json(&value)?,
241                ]))
242            }
243
244            #[::rmcp::tool(
245                name = $bundle_id_create_name,
246                title = "Register a bundle ID",
247                annotations(title = "Register a bundle ID", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
248                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."
249            )]
250            async fn bundle_id_create(
251                &self,
252                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BundleIdCreateRequest>,
253            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
254                #[allow(unused_imports)]
255                use ::smbcloud_ascapi_aso::prelude::*;
256                #[allow(unused_imports)]
257                use ::smbcloud_ascapi_signing::prelude::*;
258                let client = Self::client()?;
259                let request = parameters.0;
260                let platform = ::smbcloud_ascapi_frontend::bundle_id_platform_from_str(&request.platform)
261                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
262                let attributes = ::smbcloud_ascapi_aso::bundle_id::BundleIdCreateAttributes {
263                    identifier: request.identifier,
264                    name: request.name,
265                    platform,
266                };
267                let value = client
268                    .create_bundle_id(attributes)
269                    .await
270                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
271                Ok(::rmcp::model::CallToolResult::success(vec![
272                    ::rmcp::model::ContentBlock::json(&value)?,
273                ]))
274            }
275
276            #[::rmcp::tool(
277                name = $app_store_version_list_name,
278                title = "List app store versions",
279                annotations(title = "List app store versions", read_only_hint = true, idempotent_hint = true),
280                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."
281            )]
282            async fn app_store_version_list(
283                &self,
284                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppStoreVersionListRequest>,
285            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
286                #[allow(unused_imports)]
287                use ::smbcloud_ascapi_aso::prelude::*;
288                #[allow(unused_imports)]
289                use ::smbcloud_ascapi_signing::prelude::*;
290                let client = Self::client()?;
291                let request = parameters.0;
292                let platform = match request.platform.as_deref() {
293                    Some(value) => Some(
294                        ::smbcloud_ascapi_frontend::platform_from_str(value)
295                            .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?,
296                    ),
297                    None => None,
298                };
299                let value = client
300                    .list_app_store_versions(&request.app_id, platform)
301                    .await
302                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
303                Ok(::rmcp::model::CallToolResult::success(vec![
304                    ::rmcp::model::ContentBlock::json(&value)?,
305                ]))
306            }
307
308            #[::rmcp::tool(
309                name = $app_store_version_get_name,
310                title = "Get one app store version",
311                annotations(title = "Get one app store version", read_only_hint = true, idempotent_hint = true),
312                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 are fetched with their own tools, and its attached build with app_store_version_build_get."
313            )]
314            async fn app_store_version_get(
315                &self,
316                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
317            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
318                #[allow(unused_imports)]
319                use ::smbcloud_ascapi_aso::prelude::*;
320                #[allow(unused_imports)]
321                use ::smbcloud_ascapi_signing::prelude::*;
322                let client = Self::client()?;
323                let request = parameters.0;
324                let value = client
325                    .get_app_store_version(&request.id)
326                    .await
327                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
328                Ok(::rmcp::model::CallToolResult::success(vec![
329                    ::rmcp::model::ContentBlock::json(&value)?,
330                ]))
331            }
332
333            #[::rmcp::tool(
334                name = $app_store_version_create_name,
335                title = "Create an app store version",
336                annotations(title = "Create an app store version", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
337                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."
338            )]
339            async fn app_store_version_create(
340                &self,
341                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppStoreVersionCreateRequest>,
342            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
343                #[allow(unused_imports)]
344                use ::smbcloud_ascapi_aso::prelude::*;
345                #[allow(unused_imports)]
346                use ::smbcloud_ascapi_signing::prelude::*;
347                let client = Self::client()?;
348                let request = parameters.0;
349                let platform = ::smbcloud_ascapi_frontend::platform_from_str(&request.platform)
350                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
351                let attributes = ::smbcloud_ascapi_aso::app_store_version::AppStoreVersionCreateAttributes {
352                    platform,
353                    version_string: request.version_string,
354                    copyright: request.copyright,
355                };
356                let value = client
357                    .create_app_store_version(&request.app_id, attributes)
358                    .await
359                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
360                Ok(::rmcp::model::CallToolResult::success(vec![
361                    ::rmcp::model::ContentBlock::json(&value)?,
362                ]))
363            }
364
365            #[::rmcp::tool(
366                name = $app_store_version_delete_name,
367                title = "Delete an app store version",
368                annotations(title = "Delete an app store version", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
369                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."
370            )]
371            async fn app_store_version_delete(
372                &self,
373                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
374            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
375                #[allow(unused_imports)]
376                use ::smbcloud_ascapi_aso::prelude::*;
377                #[allow(unused_imports)]
378                use ::smbcloud_ascapi_signing::prelude::*;
379                let client = Self::client()?;
380                let request = parameters.0;
381                client
382                    .delete_app_store_version(&request.id)
383                    .await
384                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
385                Ok(::rmcp::model::CallToolResult::success(vec![
386                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
387                ]))
388            }
389
390            #[::rmcp::tool(
391                name = $app_store_version_set_build_name,
392                title = "Attach a build to a version",
393                annotations(title = "Attach a build to a version", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
394                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, then reads the build back and fails if the version does not report it; returns the version id and the attached build with its version, processing state, and export-compliance answer. 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."
395            )]
396            async fn app_store_version_set_build(
397                &self,
398                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::SetBuildRequest>,
399            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
400                #[allow(unused_imports)]
401                use ::smbcloud_ascapi_aso::prelude::*;
402                #[allow(unused_imports)]
403                use ::smbcloud_ascapi_signing::prelude::*;
404                let client = Self::client()?;
405                let request = parameters.0;
406                let attached = ::smbcloud_ascapi_frontend::attach_build(
407                    &client,
408                    &request.version_id,
409                    &request.build_id,
410                    true,
411                )
412                .await
413                .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
414                Ok(::rmcp::model::CallToolResult::success(vec![
415                    ::rmcp::model::ContentBlock::json(&attached)?,
416                ]))
417            }
418
419            #[::rmcp::tool(
420                name = $app_store_version_build_get_name,
421                title = "Get the build attached to a version",
422                annotations(title = "Get the build attached to a version", read_only_hint = true, idempotent_hint = true),
423                description = "Purpose: read which build is attached to an App Store Version, so it can be checked before the version is submitted for review. When to use vs siblings: app_store_version_get returns the version without its build; use this after app_store_version_set_build or before review_submission_submit. Behavior: returns the attached build with its id, version string, processing state, and export-compliance answer, or null when no build is 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: reports only the current attachment, not earlier builds; use build_list for the builds uploaded for an app."
424            )]
425            async fn app_store_version_build_get(
426                &self,
427                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
428            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
429                #[allow(unused_imports)]
430                use ::smbcloud_ascapi_aso::prelude::*;
431                #[allow(unused_imports)]
432                use ::smbcloud_ascapi_signing::prelude::*;
433                let client = Self::client()?;
434                let request = parameters.0;
435                let value = client
436                    .get_app_store_version_build(&request.id)
437                    .await
438                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
439                Ok(::rmcp::model::CallToolResult::success(vec![
440                    ::rmcp::model::ContentBlock::json(&value)?,
441                ]))
442            }
443
444            #[::rmcp::tool(
445                name = $app_info_localization_list_name,
446                title = "List app info localizations",
447                annotations(title = "List app info localizations", read_only_hint = true, idempotent_hint = true),
448                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."
449            )]
450            async fn app_info_localization_list(
451                &self,
452                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationListRequest>,
453            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
454                #[allow(unused_imports)]
455                use ::smbcloud_ascapi_aso::prelude::*;
456                #[allow(unused_imports)]
457                use ::smbcloud_ascapi_signing::prelude::*;
458                let client = Self::client()?;
459                let request = parameters.0;
460                let value = client
461                    .list_app_info_localizations(&request.app_info_id)
462                    .await
463                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
464                Ok(::rmcp::model::CallToolResult::success(vec![
465                    ::rmcp::model::ContentBlock::json(&value)?,
466                ]))
467            }
468
469            #[::rmcp::tool(
470                name = $app_info_localization_create_name,
471                title = "Add an app info localization",
472                annotations(title = "Add an app info localization", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
473                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."
474            )]
475            async fn app_info_localization_create(
476                &self,
477                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationCreateRequest>,
478            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
479                #[allow(unused_imports)]
480                use ::smbcloud_ascapi_aso::prelude::*;
481                #[allow(unused_imports)]
482                use ::smbcloud_ascapi_signing::prelude::*;
483                let client = Self::client()?;
484                let request = parameters.0;
485                let attributes = ::smbcloud_ascapi_aso::app_info_localization::AppInfoLocalizationCreateAttributes {
486                    locale: request.locale,
487                    name: request.name,
488                    subtitle: request.subtitle,
489                    privacy_policy_url: request.privacy_policy_url,
490                };
491                let value = client
492                    .create_app_info_localization(&request.app_info_id, attributes)
493                    .await
494                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
495                Ok(::rmcp::model::CallToolResult::success(vec![
496                    ::rmcp::model::ContentBlock::json(&value)?,
497                ]))
498            }
499
500            #[::rmcp::tool(
501                name = $app_info_localization_update_name,
502                title = "Edit an app info localization",
503                annotations(title = "Edit an app info localization", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
504                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."
505            )]
506            async fn app_info_localization_update(
507                &self,
508                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationUpdateRequest>,
509            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
510                #[allow(unused_imports)]
511                use ::smbcloud_ascapi_aso::prelude::*;
512                #[allow(unused_imports)]
513                use ::smbcloud_ascapi_signing::prelude::*;
514                let client = Self::client()?;
515                let request = parameters.0;
516                let attributes = ::smbcloud_ascapi_aso::app_info_localization::AppInfoLocalizationUpdateAttributes {
517                    name: request.name,
518                    subtitle: request.subtitle,
519                    privacy_policy_url: request.privacy_policy_url,
520                };
521                let value = client
522                    .update_app_info_localization(&request.id, attributes)
523                    .await
524                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
525                Ok(::rmcp::model::CallToolResult::success(vec![
526                    ::rmcp::model::ContentBlock::json(&value)?,
527                ]))
528            }
529
530            #[::rmcp::tool(
531                name = $app_info_localization_delete_name,
532                title = "Delete an app info localization",
533                annotations(title = "Delete an app info localization", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
534                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."
535            )]
536            async fn app_info_localization_delete(
537                &self,
538                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
539            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
540                #[allow(unused_imports)]
541                use ::smbcloud_ascapi_aso::prelude::*;
542                #[allow(unused_imports)]
543                use ::smbcloud_ascapi_signing::prelude::*;
544                let client = Self::client()?;
545                let request = parameters.0;
546                client
547                    .delete_app_info_localization(&request.id)
548                    .await
549                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
550                Ok(::rmcp::model::CallToolResult::success(vec![
551                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
552                ]))
553            }
554
555            #[::rmcp::tool(
556                name = $app_store_version_localization_list_name,
557                title = "List version localizations",
558                annotations(title = "List version localizations", read_only_hint = true, idempotent_hint = true),
559                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."
560            )]
561            async fn app_store_version_localization_list(
562                &self,
563                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationListRequest>,
564            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
565                #[allow(unused_imports)]
566                use ::smbcloud_ascapi_aso::prelude::*;
567                #[allow(unused_imports)]
568                use ::smbcloud_ascapi_signing::prelude::*;
569                let client = Self::client()?;
570                let request = parameters.0;
571                let value = client
572                    .list_app_store_version_localizations(&request.version_id)
573                    .await
574                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
575                Ok(::rmcp::model::CallToolResult::success(vec![
576                    ::rmcp::model::ContentBlock::json(&value)?,
577                ]))
578            }
579
580            #[::rmcp::tool(
581                name = $app_store_version_localization_create_name,
582                title = "Add a version localization",
583                annotations(title = "Add a version localization", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
584                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: field limits are checked before sending (4000 characters for description and release notes, 100 for keywords, 170 for promotional text), and a value over its limit returns an invalid-params error naming the field."
585            )]
586            async fn app_store_version_localization_create(
587                &self,
588                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationCreateRequest>,
589            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
590                #[allow(unused_imports)]
591                use ::smbcloud_ascapi_aso::prelude::*;
592                #[allow(unused_imports)]
593                use ::smbcloud_ascapi_signing::prelude::*;
594                let client = Self::client()?;
595                let request = parameters.0;
596                let fields = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationFields {
597                    description: request.description,
598                    keywords: request.keywords,
599                    marketing_url: request.marketing_url,
600                    promotional_text: request.promotional_text,
601                    support_url: request.support_url,
602                    whats_new: request.whats_new,
603                };
604                fields
605                    .validate()
606                    .map_err(|message| ::rmcp::model::ErrorData::invalid_params(message, None))?;
607                let attributes = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationCreateAttributes {
608                    locale: request.locale,
609                    fields,
610                };
611                let value = client
612                    .create_app_store_version_localization(&request.version_id, attributes)
613                    .await
614                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
615                Ok(::rmcp::model::CallToolResult::success(vec![
616                    ::rmcp::model::ContentBlock::json(&value)?,
617                ]))
618            }
619
620            #[::rmcp::tool(
621                name = $app_store_version_localization_update_name,
622                title = "Edit a version localization",
623                annotations(title = "Edit a version localization", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
624                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: field limits are checked before sending (4000 characters for description and release notes, 100 for keywords, 170 for promotional text) and a value over its limit returns an invalid-params error naming the field; which fields are editable depends on the version's review state, and Apple rejects edits to a version under review."
625            )]
626            async fn app_store_version_localization_update(
627                &self,
628                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationUpdateRequest>,
629            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
630                #[allow(unused_imports)]
631                use ::smbcloud_ascapi_aso::prelude::*;
632                #[allow(unused_imports)]
633                use ::smbcloud_ascapi_signing::prelude::*;
634                let client = Self::client()?;
635                let request = parameters.0;
636                let fields = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationFields {
637                    description: request.description,
638                    keywords: request.keywords,
639                    marketing_url: request.marketing_url,
640                    promotional_text: request.promotional_text,
641                    support_url: request.support_url,
642                    whats_new: request.whats_new,
643                };
644                fields
645                    .validate()
646                    .map_err(|message| ::rmcp::model::ErrorData::invalid_params(message, None))?;
647                let value = client
648                    .update_app_store_version_localization(&request.id, fields)
649                    .await
650                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
651                Ok(::rmcp::model::CallToolResult::success(vec![
652                    ::rmcp::model::ContentBlock::json(&value)?,
653                ]))
654            }
655
656            #[::rmcp::tool(
657                name = $app_store_version_localization_delete_name,
658                title = "Delete a version localization",
659                annotations(title = "Delete a version localization", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
660                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."
661            )]
662            async fn app_store_version_localization_delete(
663                &self,
664                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
665            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
666                #[allow(unused_imports)]
667                use ::smbcloud_ascapi_aso::prelude::*;
668                #[allow(unused_imports)]
669                use ::smbcloud_ascapi_signing::prelude::*;
670                let client = Self::client()?;
671                let request = parameters.0;
672                client
673                    .delete_app_store_version_localization(&request.id)
674                    .await
675                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
676                Ok(::rmcp::model::CallToolResult::success(vec![
677                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
678                ]))
679            }
680
681            #[::rmcp::tool(
682                name = $app_screenshot_set_list_name,
683                title = "List screenshot sets",
684                annotations(title = "List screenshot sets", read_only_hint = true, idempotent_hint = true),
685                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."
686            )]
687            async fn app_screenshot_set_list(
688                &self,
689                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotSetListRequest>,
690            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
691                #[allow(unused_imports)]
692                use ::smbcloud_ascapi_aso::prelude::*;
693                #[allow(unused_imports)]
694                use ::smbcloud_ascapi_signing::prelude::*;
695                let client = Self::client()?;
696                let request = parameters.0;
697                let value = client
698                    .list_app_screenshot_sets(&request.localization_id)
699                    .await
700                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
701                Ok(::rmcp::model::CallToolResult::success(vec![
702                    ::rmcp::model::ContentBlock::json(&value)?,
703                ]))
704            }
705
706            #[::rmcp::tool(
707                name = $app_screenshot_set_create_name,
708                title = "Create a screenshot set",
709                annotations(title = "Create a screenshot set", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
710                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."
711            )]
712            async fn app_screenshot_set_create(
713                &self,
714                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotSetCreateRequest>,
715            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
716                #[allow(unused_imports)]
717                use ::smbcloud_ascapi_aso::prelude::*;
718                #[allow(unused_imports)]
719                use ::smbcloud_ascapi_signing::prelude::*;
720                let client = Self::client()?;
721                let request = parameters.0;
722                let display_type = ::smbcloud_ascapi_frontend::display_type_from_str(&request.display_type)
723                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
724                let attributes = ::smbcloud_ascapi_aso::app_screenshot_set::AppScreenshotSetCreateAttributes {
725                    screenshot_display_type: display_type,
726                };
727                let value = client
728                    .create_app_screenshot_set(&request.localization_id, attributes)
729                    .await
730                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
731                Ok(::rmcp::model::CallToolResult::success(vec![
732                    ::rmcp::model::ContentBlock::json(&value)?,
733                ]))
734            }
735
736            #[::rmcp::tool(
737                name = $app_screenshot_set_delete_name,
738                title = "Delete a screenshot set",
739                annotations(title = "Delete a screenshot set", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
740                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."
741            )]
742            async fn app_screenshot_set_delete(
743                &self,
744                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
745            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
746                #[allow(unused_imports)]
747                use ::smbcloud_ascapi_aso::prelude::*;
748                #[allow(unused_imports)]
749                use ::smbcloud_ascapi_signing::prelude::*;
750                let client = Self::client()?;
751                let request = parameters.0;
752                client
753                    .delete_app_screenshot_set(&request.id)
754                    .await
755                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
756                Ok(::rmcp::model::CallToolResult::success(vec![
757                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
758                ]))
759            }
760
761            #[::rmcp::tool(
762                name = $app_screenshot_list_name,
763                title = "List screenshots",
764                annotations(title = "List screenshots", read_only_hint = true, idempotent_hint = true),
765                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."
766            )]
767            async fn app_screenshot_list(
768                &self,
769                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotListRequest>,
770            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
771                #[allow(unused_imports)]
772                use ::smbcloud_ascapi_aso::prelude::*;
773                #[allow(unused_imports)]
774                use ::smbcloud_ascapi_signing::prelude::*;
775                let client = Self::client()?;
776                let request = parameters.0;
777                let value = client
778                    .list_app_screenshots(&request.screenshot_set_id)
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_upload_name,
788                title = "Upload a screenshot",
789                annotations(title = "Upload a screenshot", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
790                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."
791            )]
792            async fn app_screenshot_upload(
793                &self,
794                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotUploadRequest>,
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                let path = ::std::path::PathBuf::from(&request.file_path);
803                let bytes = ::std::fs::read(&path).map_err(|error| {
804                    ::rmcp::model::ErrorData::invalid_request(
805                        format!("reading {}: {error}", path.display()),
806                        None,
807                    )
808                })?;
809                let file_name = path
810                    .file_name()
811                    .map(|name| name.to_string_lossy().into_owned())
812                    .ok_or_else(|| {
813                        ::rmcp::model::ErrorData::invalid_request(
814                            format!("{} has no file name", path.display()),
815                            None,
816                        )
817                    })?;
818                let value = client
819                    .upload_app_screenshot(&request.screenshot_set_id, file_name, bytes)
820                    .await
821                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
822                Ok(::rmcp::model::CallToolResult::success(vec![
823                    ::rmcp::model::ContentBlock::json(&value)?,
824                ]))
825            }
826
827            #[::rmcp::tool(
828                name = $app_screenshot_delete_name,
829                title = "Delete a screenshot",
830                annotations(title = "Delete a screenshot", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
831                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."
832            )]
833            async fn app_screenshot_delete(
834                &self,
835                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
836            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
837                #[allow(unused_imports)]
838                use ::smbcloud_ascapi_aso::prelude::*;
839                #[allow(unused_imports)]
840                use ::smbcloud_ascapi_signing::prelude::*;
841                let client = Self::client()?;
842                let request = parameters.0;
843                client
844                    .delete_app_screenshot(&request.id)
845                    .await
846                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
847                Ok(::rmcp::model::CallToolResult::success(vec![
848                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
849                ]))
850            }
851
852            #[::rmcp::tool(
853                name = $certificate_list_name,
854                title = "List signing certificates",
855                annotations(title = "List signing certificates", read_only_hint = true, idempotent_hint = true),
856                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."
857            )]
858            async fn certificate_list(
859                &self,
860                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::CertificateListRequest>,
861            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
862                #[allow(unused_imports)]
863                use ::smbcloud_ascapi_aso::prelude::*;
864                #[allow(unused_imports)]
865                use ::smbcloud_ascapi_signing::prelude::*;
866                let client = Self::client()?;
867                let request = parameters.0;
868                let filter = match request.certificate_type.as_deref() {
869                    Some(value) => Some(
870                        ::smbcloud_ascapi_frontend::certificate_type_from_str(value)
871                            .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?,
872                    ),
873                    None => None,
874                };
875                let mut certificates = client.list_certificates(filter).await.map_err(|error| {
876                    ::rmcp::model::ErrorData::internal_error(error.to_string(), None)
877                })?;
878                certificates.sort_by(|a, b| {
879                    a.attributes.expiration_date.cmp(&b.attributes.expiration_date)
880                });
881                let now = ::smbcloud_ascapi_frontend::now_iso8601();
882                let summaries: Vec<_> = certificates
883                    .iter()
884                    .map(|certificate| ::smbcloud_ascapi_frontend::CertificateSummary::from_resource(certificate, &now))
885                    .collect();
886                Ok(::rmcp::model::CallToolResult::success(vec![
887                    ::rmcp::model::ContentBlock::json(&summaries)?,
888                ]))
889            }
890
891            #[::rmcp::tool(
892                name = $certificate_create_name,
893                title = "Issue a signing certificate",
894                annotations(title = "Issue a signing certificate", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
895                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."
896            )]
897            async fn certificate_create(
898                &self,
899                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::CertificateCreateRequest>,
900            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
901                #[allow(unused_imports)]
902                use ::smbcloud_ascapi_aso::prelude::*;
903                #[allow(unused_imports)]
904                use ::smbcloud_ascapi_signing::prelude::*;
905                let client = Self::client()?;
906                let request = parameters.0;
907                let certificate_type = ::smbcloud_ascapi_frontend::certificate_type_from_str(&request.certificate_type)
908                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
909                let common_name = request
910                    .common_name
911                    .unwrap_or_else(|| "smbcloud-ascapi".to_string());
912                let out_dir = ::std::path::PathBuf::from(&request.out_dir);
913                let outcome = ::smbcloud_ascapi_frontend::issue_certificate(
914                    &client,
915                    certificate_type,
916                    &common_name,
917                    &out_dir,
918                )
919                .await
920                .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
921                Ok(::rmcp::model::CallToolResult::success(vec![
922                    ::rmcp::model::ContentBlock::json(&outcome)?,
923                ]))
924            }
925
926            #[::rmcp::tool(
927                name = $beta_group_list_name,
928                title = "List beta groups",
929                annotations(title = "List beta groups", read_only_hint = true, idempotent_hint = true),
930                description = "Purpose: list every TestFlight beta group (internal and external) for an app, so an agent knows which group ids are available before adding builds or testers. When to use vs siblings: call this first to discover group ids; use beta_app_review_detail_get to retrieve the contact info Apple's beta review team will see. Behavior: returns each group's id, name, whether it is an internal group, whether it requires a public link, the public link URL (when enabled), and tester counts where available. 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: tester emails are not included; use the App Store Connect portal or the CLI to enumerate individual testers."
931            )]
932            async fn beta_group_list(
933                &self,
934                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BetaGroupListRequest>,
935            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
936                #[allow(unused_imports)]
937                use ::smbcloud_ascapi_aso::prelude::*;
938                let client = Self::client()?;
939                let request = parameters.0;
940                let groups = client
941                    .list_beta_groups(&request.app_id)
942                    .await
943                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
944                Ok(::rmcp::model::CallToolResult::success(vec![
945                    ::rmcp::model::ContentBlock::json(&groups)?,
946                ]))
947            }
948
949            #[::rmcp::tool(
950                name = $beta_app_review_detail_get_name,
951                title = "Get beta app review contact detail",
952                annotations(title = "Get beta app review contact detail", read_only_hint = true, idempotent_hint = true),
953                description = "Purpose: retrieve the contact information Apple's TestFlight review team will use when evaluating a beta submission, such as the first name, last name, phone number, email, and any demo account credentials. When to use vs siblings: call this to verify or surface the review contact before submitting a build for beta review; use beta_group_list to discover available groups. Behavior: returns the beta app review detail resource for the given app 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: updating the contact detail is a CLI-only operation."
954            )]
955            async fn beta_app_review_detail_get(
956                &self,
957                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BetaAppReviewDetailGetRequest>,
958            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
959                #[allow(unused_imports)]
960                use ::smbcloud_ascapi_aso::prelude::*;
961                let client = Self::client()?;
962                let request = parameters.0;
963                let detail = client
964                    .get_beta_app_review_detail(&request.app_id)
965                    .await
966                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
967                Ok(::rmcp::model::CallToolResult::success(vec![
968                    ::rmcp::model::ContentBlock::json(&detail)?,
969                ]))
970            }
971
972            #[::rmcp::tool(
973                name = $play_listing_list_name,
974                title = "List Play listings",
975                annotations(title = "List Play listings", read_only_hint = true, idempotent_hint = true),
976                description = "Purpose: read every localized Google Play store listing of an app. When to use vs siblings: use this for listing text; play_images_list reads screenshots and graphics, play_track_get reads release notes. Behavior: opens a Play edit, reads the listings (language, title, short and full description, video) and discards the edit, so nothing changes. Prerequisites: a Google Play service account with access to the app, given as a key file at PLAY_SERVICE_ACCOUNT_JSON_PATH or inline in PLAY_SERVICE_ACCOUNT_JSON. Failure modes: missing credentials return an invalid-request error naming what is absent; Google Play errors are returned verbatim with their status. Limitations: read-only; changing a listing is available only through the asc play listings update command."
977            )]
978            async fn play_listing_list(
979                &self,
980                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::PlayPackageRequest>,
981            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
982                use ::smbcloud_ascapi_frontend::play;
983                let client = play::play_client(None)
984                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
985                let request = parameters.0;
986                let value = play::read_listings(&client, &request.package_name)
987                    .await
988                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
989                Ok(::rmcp::model::CallToolResult::success(vec![
990                    ::rmcp::model::ContentBlock::json(&value)?,
991                ]))
992            }
993
994            #[::rmcp::tool(
995                name = $play_images_list_name,
996                title = "List Play listing images",
997                annotations(title = "List Play listing images", read_only_hint = true, idempotent_hint = true),
998                description = "Purpose: list the images in one bucket (phone screenshots, tablet screenshots, feature graphic or icon) of one Google Play listing locale. When to use vs siblings: use play_listing_list for text and play_track_get for release notes. Behavior: opens a Play edit, reads the bucket's image ids, URLs and hashes, and discards the edit. Prerequisites: a Google Play service account with access to the app, given as a key file at PLAY_SERVICE_ACCOUNT_JSON_PATH or inline in PLAY_SERVICE_ACCOUNT_JSON. Failure modes: missing credentials return an invalid-request error naming what is absent; Google Play errors are returned verbatim with their status. Limitations: read-only; replacing images is available only through the asc play images replace command."
999            )]
1000            async fn play_images_list(
1001                &self,
1002                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::PlayImagesListRequest>,
1003            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
1004                use ::smbcloud_ascapi_frontend::play;
1005                let client = play::play_client(None)
1006                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
1007                let request = parameters.0;
1008                let image_type = play::image_type_from_str(&request.image_type)
1009                    .map_err(|error| ::rmcp::model::ErrorData::invalid_params(error, None))?;
1010                let value = play::read_images(
1011                    &client,
1012                    &request.package_name,
1013                    &request.locale,
1014                    image_type,
1015                )
1016                .await
1017                .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
1018                Ok(::rmcp::model::CallToolResult::success(vec![
1019                    ::rmcp::model::ContentBlock::json(&value)?,
1020                ]))
1021            }
1022
1023            #[::rmcp::tool(
1024                name = $play_track_get_name,
1025                title = "Get a Play track",
1026                annotations(title = "Get a Play track", read_only_hint = true, idempotent_hint = true),
1027                description = "Purpose: read a Google Play release track with its releases, version codes, status and per-language release notes. When to use vs siblings: use play_listing_list for store listing text and play_images_list for images. Behavior: opens a Play edit, reads the track and discards the edit. Prerequisites: a Google Play service account with access to the app, given as a key file at PLAY_SERVICE_ACCOUNT_JSON_PATH or inline in PLAY_SERVICE_ACCOUNT_JSON. Failure modes: missing credentials return an invalid-request error naming what is absent; Google Play errors are returned verbatim with their status. Limitations: read-only; setting release notes is available only through the asc play tracks set-notes command."
1028            )]
1029            async fn play_track_get(
1030                &self,
1031                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::PlayTrackGetRequest>,
1032            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
1033                use ::smbcloud_ascapi_frontend::play;
1034                let client = play::play_client(None)
1035                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
1036                let request = parameters.0;
1037                let value = play::read_track(&client, &request.package_name, &request.track)
1038                    .await
1039                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
1040                Ok(::rmcp::model::CallToolResult::success(vec![
1041                    ::rmcp::model::ContentBlock::json(&value)?,
1042                ]))
1043            }
1044        }
1045    };
1046}