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    ) => {
74        #[::rmcp::tool_router(router = ascapi_tool_router, vis = "pub(crate)")]
75        impl $server {
76            fn client() -> ::std::result::Result<::smbcloud_ascapi_core::Client, ::rmcp::model::ErrorData> {
77                let api_key = ::smbcloud_ascapi_frontend::api_key_from_env()
78                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
79                Ok(::smbcloud_ascapi_core::Client::new(api_key))
80            }
81
82            #[::rmcp::tool(
83                name = $app_list_name,
84                title = "List apps",
85                annotations(title = "List apps", read_only_hint = true, idempotent_hint = true),
86                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."
87            )]
88            async fn app_list(
89                &self,
90                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppListRequest>,
91            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
92                #[allow(unused_imports)]
93                use ::smbcloud_ascapi_aso::prelude::*;
94                #[allow(unused_imports)]
95                use ::smbcloud_ascapi_signing::prelude::*;
96                let client = Self::client()?;
97                let request = parameters.0;
98                let value = client
99                    .list_apps(request.bundle_id.as_deref())
100                    .await
101                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
102                Ok(::rmcp::model::CallToolResult::success(vec![
103                    ::rmcp::model::ContentBlock::json(&value)?,
104                ]))
105            }
106
107            #[::rmcp::tool(
108                name = $app_get_name,
109                title = "Get one app",
110                annotations(title = "Get one app", read_only_hint = true, idempotent_hint = true),
111                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."
112            )]
113            async fn app_get(
114                &self,
115                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppIdRequest>,
116            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
117                #[allow(unused_imports)]
118                use ::smbcloud_ascapi_aso::prelude::*;
119                #[allow(unused_imports)]
120                use ::smbcloud_ascapi_signing::prelude::*;
121                let client = Self::client()?;
122                let request = parameters.0;
123                let value = client
124                    .get_app(&request.app_id)
125                    .await
126                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
127                Ok(::rmcp::model::CallToolResult::success(vec![
128                    ::rmcp::model::ContentBlock::json(&value)?,
129                ]))
130            }
131
132            #[::rmcp::tool(
133                name = $app_update_name,
134                title = "Update app attributes",
135                annotations(title = "Update app attributes", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
136                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."
137            )]
138            async fn app_update(
139                &self,
140                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppUpdateRequest>,
141            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
142                #[allow(unused_imports)]
143                use ::smbcloud_ascapi_aso::prelude::*;
144                #[allow(unused_imports)]
145                use ::smbcloud_ascapi_signing::prelude::*;
146                let client = Self::client()?;
147                let request = parameters.0;
148                let attributes = ::smbcloud_ascapi_aso::app::AppUpdateAttributes {
149                    primary_locale: request.primary_locale,
150                    content_rights_declaration: request.content_rights_declaration,
151                };
152                let value = client
153                    .update_app(&request.app_id, attributes)
154                    .await
155                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
156                Ok(::rmcp::model::CallToolResult::success(vec![
157                    ::rmcp::model::ContentBlock::json(&value)?,
158                ]))
159            }
160
161            #[::rmcp::tool(
162                name = $app_info_list_name,
163                title = "List app infos",
164                annotations(title = "List app infos", read_only_hint = true, idempotent_hint = true),
165                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."
166            )]
167            async fn app_info_list(
168                &self,
169                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppIdRequest>,
170            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
171                #[allow(unused_imports)]
172                use ::smbcloud_ascapi_aso::prelude::*;
173                #[allow(unused_imports)]
174                use ::smbcloud_ascapi_signing::prelude::*;
175                let client = Self::client()?;
176                let request = parameters.0;
177                let value = client
178                    .list_app_infos(&request.app_id)
179                    .await
180                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
181                Ok(::rmcp::model::CallToolResult::success(vec![
182                    ::rmcp::model::ContentBlock::json(&value)?,
183                ]))
184            }
185
186            #[::rmcp::tool(
187                name = $build_list_name,
188                title = "List builds",
189                annotations(title = "List builds", read_only_hint = true, idempotent_hint = true),
190                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."
191            )]
192            async fn build_list(
193                &self,
194                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BuildListRequest>,
195            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
196                #[allow(unused_imports)]
197                use ::smbcloud_ascapi_aso::prelude::*;
198                #[allow(unused_imports)]
199                use ::smbcloud_ascapi_signing::prelude::*;
200                let client = Self::client()?;
201                let request = parameters.0;
202                let filter = ::smbcloud_ascapi_aso::build::BuildFilter {
203                    version: request.version,
204                    pre_release_version: request.pre_release_version,
205                    processing_state: request.processing_state,
206                };
207                let value = client
208                    .list_builds(&request.app_id, &filter)
209                    .await
210                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
211                Ok(::rmcp::model::CallToolResult::success(vec![
212                    ::rmcp::model::ContentBlock::json(&value)?,
213                ]))
214            }
215
216            #[::rmcp::tool(
217                name = $bundle_id_list_name,
218                title = "List bundle IDs",
219                annotations(title = "List bundle IDs", read_only_hint = true, idempotent_hint = true),
220                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."
221            )]
222            async fn bundle_id_list(
223                &self,
224                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BundleIdListRequest>,
225            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
226                #[allow(unused_imports)]
227                use ::smbcloud_ascapi_aso::prelude::*;
228                #[allow(unused_imports)]
229                use ::smbcloud_ascapi_signing::prelude::*;
230                let client = Self::client()?;
231                let request = parameters.0;
232                let value = client
233                    .list_bundle_ids(request.identifier.as_deref())
234                    .await
235                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
236                Ok(::rmcp::model::CallToolResult::success(vec![
237                    ::rmcp::model::ContentBlock::json(&value)?,
238                ]))
239            }
240
241            #[::rmcp::tool(
242                name = $bundle_id_create_name,
243                title = "Register a bundle ID",
244                annotations(title = "Register a bundle ID", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
245                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."
246            )]
247            async fn bundle_id_create(
248                &self,
249                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BundleIdCreateRequest>,
250            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
251                #[allow(unused_imports)]
252                use ::smbcloud_ascapi_aso::prelude::*;
253                #[allow(unused_imports)]
254                use ::smbcloud_ascapi_signing::prelude::*;
255                let client = Self::client()?;
256                let request = parameters.0;
257                let platform = ::smbcloud_ascapi_frontend::bundle_id_platform_from_str(&request.platform)
258                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
259                let attributes = ::smbcloud_ascapi_aso::bundle_id::BundleIdCreateAttributes {
260                    identifier: request.identifier,
261                    name: request.name,
262                    platform,
263                };
264                let value = client
265                    .create_bundle_id(attributes)
266                    .await
267                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
268                Ok(::rmcp::model::CallToolResult::success(vec![
269                    ::rmcp::model::ContentBlock::json(&value)?,
270                ]))
271            }
272
273            #[::rmcp::tool(
274                name = $app_store_version_list_name,
275                title = "List app store versions",
276                annotations(title = "List app store versions", read_only_hint = true, idempotent_hint = true),
277                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."
278            )]
279            async fn app_store_version_list(
280                &self,
281                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppStoreVersionListRequest>,
282            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
283                #[allow(unused_imports)]
284                use ::smbcloud_ascapi_aso::prelude::*;
285                #[allow(unused_imports)]
286                use ::smbcloud_ascapi_signing::prelude::*;
287                let client = Self::client()?;
288                let request = parameters.0;
289                let platform = match request.platform.as_deref() {
290                    Some(value) => Some(
291                        ::smbcloud_ascapi_frontend::platform_from_str(value)
292                            .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?,
293                    ),
294                    None => None,
295                };
296                let value = client
297                    .list_app_store_versions(&request.app_id, platform)
298                    .await
299                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
300                Ok(::rmcp::model::CallToolResult::success(vec![
301                    ::rmcp::model::ContentBlock::json(&value)?,
302                ]))
303            }
304
305            #[::rmcp::tool(
306                name = $app_store_version_get_name,
307                title = "Get one app store version",
308                annotations(title = "Get one app store version", read_only_hint = true, idempotent_hint = true),
309                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."
310            )]
311            async fn app_store_version_get(
312                &self,
313                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
314            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
315                #[allow(unused_imports)]
316                use ::smbcloud_ascapi_aso::prelude::*;
317                #[allow(unused_imports)]
318                use ::smbcloud_ascapi_signing::prelude::*;
319                let client = Self::client()?;
320                let request = parameters.0;
321                let value = client
322                    .get_app_store_version(&request.id)
323                    .await
324                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
325                Ok(::rmcp::model::CallToolResult::success(vec![
326                    ::rmcp::model::ContentBlock::json(&value)?,
327                ]))
328            }
329
330            #[::rmcp::tool(
331                name = $app_store_version_create_name,
332                title = "Create an app store version",
333                annotations(title = "Create an app store version", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
334                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."
335            )]
336            async fn app_store_version_create(
337                &self,
338                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppStoreVersionCreateRequest>,
339            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
340                #[allow(unused_imports)]
341                use ::smbcloud_ascapi_aso::prelude::*;
342                #[allow(unused_imports)]
343                use ::smbcloud_ascapi_signing::prelude::*;
344                let client = Self::client()?;
345                let request = parameters.0;
346                let platform = ::smbcloud_ascapi_frontend::platform_from_str(&request.platform)
347                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
348                let attributes = ::smbcloud_ascapi_aso::app_store_version::AppStoreVersionCreateAttributes {
349                    platform,
350                    version_string: request.version_string,
351                    copyright: request.copyright,
352                };
353                let value = client
354                    .create_app_store_version(&request.app_id, attributes)
355                    .await
356                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
357                Ok(::rmcp::model::CallToolResult::success(vec![
358                    ::rmcp::model::ContentBlock::json(&value)?,
359                ]))
360            }
361
362            #[::rmcp::tool(
363                name = $app_store_version_delete_name,
364                title = "Delete an app store version",
365                annotations(title = "Delete an app store version", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
366                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."
367            )]
368            async fn app_store_version_delete(
369                &self,
370                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
371            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
372                #[allow(unused_imports)]
373                use ::smbcloud_ascapi_aso::prelude::*;
374                #[allow(unused_imports)]
375                use ::smbcloud_ascapi_signing::prelude::*;
376                let client = Self::client()?;
377                let request = parameters.0;
378                client
379                    .delete_app_store_version(&request.id)
380                    .await
381                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
382                Ok(::rmcp::model::CallToolResult::success(vec![
383                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
384                ]))
385            }
386
387            #[::rmcp::tool(
388                name = $app_store_version_set_build_name,
389                title = "Attach a build to a version",
390                annotations(title = "Attach a build to a version", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
391                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."
392            )]
393            async fn app_store_version_set_build(
394                &self,
395                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::SetBuildRequest>,
396            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
397                #[allow(unused_imports)]
398                use ::smbcloud_ascapi_aso::prelude::*;
399                #[allow(unused_imports)]
400                use ::smbcloud_ascapi_signing::prelude::*;
401                let client = Self::client()?;
402                let request = parameters.0;
403                let attached = ::smbcloud_ascapi_frontend::attach_build(
404                    &client,
405                    &request.version_id,
406                    &request.build_id,
407                    true,
408                )
409                .await
410                .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
411                Ok(::rmcp::model::CallToolResult::success(vec![
412                    ::rmcp::model::ContentBlock::json(&attached)?,
413                ]))
414            }
415
416            #[::rmcp::tool(
417                name = $app_store_version_build_get_name,
418                title = "Get the build attached to a version",
419                annotations(title = "Get the build attached to a version", read_only_hint = true, idempotent_hint = true),
420                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."
421            )]
422            async fn app_store_version_build_get(
423                &self,
424                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
425            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
426                #[allow(unused_imports)]
427                use ::smbcloud_ascapi_aso::prelude::*;
428                #[allow(unused_imports)]
429                use ::smbcloud_ascapi_signing::prelude::*;
430                let client = Self::client()?;
431                let request = parameters.0;
432                let value = client
433                    .get_app_store_version_build(&request.id)
434                    .await
435                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
436                Ok(::rmcp::model::CallToolResult::success(vec![
437                    ::rmcp::model::ContentBlock::json(&value)?,
438                ]))
439            }
440
441            #[::rmcp::tool(
442                name = $app_info_localization_list_name,
443                title = "List app info localizations",
444                annotations(title = "List app info localizations", read_only_hint = true, idempotent_hint = true),
445                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."
446            )]
447            async fn app_info_localization_list(
448                &self,
449                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationListRequest>,
450            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
451                #[allow(unused_imports)]
452                use ::smbcloud_ascapi_aso::prelude::*;
453                #[allow(unused_imports)]
454                use ::smbcloud_ascapi_signing::prelude::*;
455                let client = Self::client()?;
456                let request = parameters.0;
457                let value = client
458                    .list_app_info_localizations(&request.app_info_id)
459                    .await
460                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
461                Ok(::rmcp::model::CallToolResult::success(vec![
462                    ::rmcp::model::ContentBlock::json(&value)?,
463                ]))
464            }
465
466            #[::rmcp::tool(
467                name = $app_info_localization_create_name,
468                title = "Add an app info localization",
469                annotations(title = "Add an app info localization", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
470                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."
471            )]
472            async fn app_info_localization_create(
473                &self,
474                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationCreateRequest>,
475            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
476                #[allow(unused_imports)]
477                use ::smbcloud_ascapi_aso::prelude::*;
478                #[allow(unused_imports)]
479                use ::smbcloud_ascapi_signing::prelude::*;
480                let client = Self::client()?;
481                let request = parameters.0;
482                let attributes = ::smbcloud_ascapi_aso::app_info_localization::AppInfoLocalizationCreateAttributes {
483                    locale: request.locale,
484                    name: request.name,
485                    subtitle: request.subtitle,
486                    privacy_policy_url: request.privacy_policy_url,
487                };
488                let value = client
489                    .create_app_info_localization(&request.app_info_id, attributes)
490                    .await
491                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
492                Ok(::rmcp::model::CallToolResult::success(vec![
493                    ::rmcp::model::ContentBlock::json(&value)?,
494                ]))
495            }
496
497            #[::rmcp::tool(
498                name = $app_info_localization_update_name,
499                title = "Edit an app info localization",
500                annotations(title = "Edit an app info localization", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
501                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."
502            )]
503            async fn app_info_localization_update(
504                &self,
505                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationUpdateRequest>,
506            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
507                #[allow(unused_imports)]
508                use ::smbcloud_ascapi_aso::prelude::*;
509                #[allow(unused_imports)]
510                use ::smbcloud_ascapi_signing::prelude::*;
511                let client = Self::client()?;
512                let request = parameters.0;
513                let attributes = ::smbcloud_ascapi_aso::app_info_localization::AppInfoLocalizationUpdateAttributes {
514                    name: request.name,
515                    subtitle: request.subtitle,
516                    privacy_policy_url: request.privacy_policy_url,
517                };
518                let value = client
519                    .update_app_info_localization(&request.id, attributes)
520                    .await
521                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
522                Ok(::rmcp::model::CallToolResult::success(vec![
523                    ::rmcp::model::ContentBlock::json(&value)?,
524                ]))
525            }
526
527            #[::rmcp::tool(
528                name = $app_info_localization_delete_name,
529                title = "Delete an app info localization",
530                annotations(title = "Delete an app info localization", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
531                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."
532            )]
533            async fn app_info_localization_delete(
534                &self,
535                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
536            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
537                #[allow(unused_imports)]
538                use ::smbcloud_ascapi_aso::prelude::*;
539                #[allow(unused_imports)]
540                use ::smbcloud_ascapi_signing::prelude::*;
541                let client = Self::client()?;
542                let request = parameters.0;
543                client
544                    .delete_app_info_localization(&request.id)
545                    .await
546                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
547                Ok(::rmcp::model::CallToolResult::success(vec![
548                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
549                ]))
550            }
551
552            #[::rmcp::tool(
553                name = $app_store_version_localization_list_name,
554                title = "List version localizations",
555                annotations(title = "List version localizations", read_only_hint = true, idempotent_hint = true),
556                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."
557            )]
558            async fn app_store_version_localization_list(
559                &self,
560                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationListRequest>,
561            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
562                #[allow(unused_imports)]
563                use ::smbcloud_ascapi_aso::prelude::*;
564                #[allow(unused_imports)]
565                use ::smbcloud_ascapi_signing::prelude::*;
566                let client = Self::client()?;
567                let request = parameters.0;
568                let value = client
569                    .list_app_store_version_localizations(&request.version_id)
570                    .await
571                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
572                Ok(::rmcp::model::CallToolResult::success(vec![
573                    ::rmcp::model::ContentBlock::json(&value)?,
574                ]))
575            }
576
577            #[::rmcp::tool(
578                name = $app_store_version_localization_create_name,
579                title = "Add a version localization",
580                annotations(title = "Add a version localization", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
581                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."
582            )]
583            async fn app_store_version_localization_create(
584                &self,
585                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationCreateRequest>,
586            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
587                #[allow(unused_imports)]
588                use ::smbcloud_ascapi_aso::prelude::*;
589                #[allow(unused_imports)]
590                use ::smbcloud_ascapi_signing::prelude::*;
591                let client = Self::client()?;
592                let request = parameters.0;
593                let fields = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationFields {
594                    description: request.description,
595                    keywords: request.keywords,
596                    marketing_url: request.marketing_url,
597                    promotional_text: request.promotional_text,
598                    support_url: request.support_url,
599                    whats_new: request.whats_new,
600                };
601                fields
602                    .validate()
603                    .map_err(|message| ::rmcp::model::ErrorData::invalid_params(message, None))?;
604                let attributes = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationCreateAttributes {
605                    locale: request.locale,
606                    fields,
607                };
608                let value = client
609                    .create_app_store_version_localization(&request.version_id, attributes)
610                    .await
611                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
612                Ok(::rmcp::model::CallToolResult::success(vec![
613                    ::rmcp::model::ContentBlock::json(&value)?,
614                ]))
615            }
616
617            #[::rmcp::tool(
618                name = $app_store_version_localization_update_name,
619                title = "Edit a version localization",
620                annotations(title = "Edit a version localization", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
621                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."
622            )]
623            async fn app_store_version_localization_update(
624                &self,
625                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationUpdateRequest>,
626            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
627                #[allow(unused_imports)]
628                use ::smbcloud_ascapi_aso::prelude::*;
629                #[allow(unused_imports)]
630                use ::smbcloud_ascapi_signing::prelude::*;
631                let client = Self::client()?;
632                let request = parameters.0;
633                let fields = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationFields {
634                    description: request.description,
635                    keywords: request.keywords,
636                    marketing_url: request.marketing_url,
637                    promotional_text: request.promotional_text,
638                    support_url: request.support_url,
639                    whats_new: request.whats_new,
640                };
641                fields
642                    .validate()
643                    .map_err(|message| ::rmcp::model::ErrorData::invalid_params(message, None))?;
644                let value = client
645                    .update_app_store_version_localization(&request.id, fields)
646                    .await
647                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
648                Ok(::rmcp::model::CallToolResult::success(vec![
649                    ::rmcp::model::ContentBlock::json(&value)?,
650                ]))
651            }
652
653            #[::rmcp::tool(
654                name = $app_store_version_localization_delete_name,
655                title = "Delete a version localization",
656                annotations(title = "Delete a version localization", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
657                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."
658            )]
659            async fn app_store_version_localization_delete(
660                &self,
661                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
662            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
663                #[allow(unused_imports)]
664                use ::smbcloud_ascapi_aso::prelude::*;
665                #[allow(unused_imports)]
666                use ::smbcloud_ascapi_signing::prelude::*;
667                let client = Self::client()?;
668                let request = parameters.0;
669                client
670                    .delete_app_store_version_localization(&request.id)
671                    .await
672                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
673                Ok(::rmcp::model::CallToolResult::success(vec![
674                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
675                ]))
676            }
677
678            #[::rmcp::tool(
679                name = $app_screenshot_set_list_name,
680                title = "List screenshot sets",
681                annotations(title = "List screenshot sets", read_only_hint = true, idempotent_hint = true),
682                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."
683            )]
684            async fn app_screenshot_set_list(
685                &self,
686                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotSetListRequest>,
687            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
688                #[allow(unused_imports)]
689                use ::smbcloud_ascapi_aso::prelude::*;
690                #[allow(unused_imports)]
691                use ::smbcloud_ascapi_signing::prelude::*;
692                let client = Self::client()?;
693                let request = parameters.0;
694                let value = client
695                    .list_app_screenshot_sets(&request.localization_id)
696                    .await
697                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
698                Ok(::rmcp::model::CallToolResult::success(vec![
699                    ::rmcp::model::ContentBlock::json(&value)?,
700                ]))
701            }
702
703            #[::rmcp::tool(
704                name = $app_screenshot_set_create_name,
705                title = "Create a screenshot set",
706                annotations(title = "Create a screenshot set", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
707                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."
708            )]
709            async fn app_screenshot_set_create(
710                &self,
711                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotSetCreateRequest>,
712            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
713                #[allow(unused_imports)]
714                use ::smbcloud_ascapi_aso::prelude::*;
715                #[allow(unused_imports)]
716                use ::smbcloud_ascapi_signing::prelude::*;
717                let client = Self::client()?;
718                let request = parameters.0;
719                let display_type = ::smbcloud_ascapi_frontend::display_type_from_str(&request.display_type)
720                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
721                let attributes = ::smbcloud_ascapi_aso::app_screenshot_set::AppScreenshotSetCreateAttributes {
722                    screenshot_display_type: display_type,
723                };
724                let value = client
725                    .create_app_screenshot_set(&request.localization_id, attributes)
726                    .await
727                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
728                Ok(::rmcp::model::CallToolResult::success(vec![
729                    ::rmcp::model::ContentBlock::json(&value)?,
730                ]))
731            }
732
733            #[::rmcp::tool(
734                name = $app_screenshot_set_delete_name,
735                title = "Delete a screenshot set",
736                annotations(title = "Delete a screenshot set", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
737                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."
738            )]
739            async fn app_screenshot_set_delete(
740                &self,
741                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
742            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
743                #[allow(unused_imports)]
744                use ::smbcloud_ascapi_aso::prelude::*;
745                #[allow(unused_imports)]
746                use ::smbcloud_ascapi_signing::prelude::*;
747                let client = Self::client()?;
748                let request = parameters.0;
749                client
750                    .delete_app_screenshot_set(&request.id)
751                    .await
752                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
753                Ok(::rmcp::model::CallToolResult::success(vec![
754                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
755                ]))
756            }
757
758            #[::rmcp::tool(
759                name = $app_screenshot_list_name,
760                title = "List screenshots",
761                annotations(title = "List screenshots", read_only_hint = true, idempotent_hint = true),
762                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."
763            )]
764            async fn app_screenshot_list(
765                &self,
766                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotListRequest>,
767            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
768                #[allow(unused_imports)]
769                use ::smbcloud_ascapi_aso::prelude::*;
770                #[allow(unused_imports)]
771                use ::smbcloud_ascapi_signing::prelude::*;
772                let client = Self::client()?;
773                let request = parameters.0;
774                let value = client
775                    .list_app_screenshots(&request.screenshot_set_id)
776                    .await
777                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
778                Ok(::rmcp::model::CallToolResult::success(vec![
779                    ::rmcp::model::ContentBlock::json(&value)?,
780                ]))
781            }
782
783            #[::rmcp::tool(
784                name = $app_screenshot_upload_name,
785                title = "Upload a screenshot",
786                annotations(title = "Upload a screenshot", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
787                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."
788            )]
789            async fn app_screenshot_upload(
790                &self,
791                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotUploadRequest>,
792            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
793                #[allow(unused_imports)]
794                use ::smbcloud_ascapi_aso::prelude::*;
795                #[allow(unused_imports)]
796                use ::smbcloud_ascapi_signing::prelude::*;
797                let client = Self::client()?;
798                let request = parameters.0;
799                let path = ::std::path::PathBuf::from(&request.file_path);
800                let bytes = ::std::fs::read(&path).map_err(|error| {
801                    ::rmcp::model::ErrorData::invalid_request(
802                        format!("reading {}: {error}", path.display()),
803                        None,
804                    )
805                })?;
806                let file_name = path
807                    .file_name()
808                    .map(|name| name.to_string_lossy().into_owned())
809                    .ok_or_else(|| {
810                        ::rmcp::model::ErrorData::invalid_request(
811                            format!("{} has no file name", path.display()),
812                            None,
813                        )
814                    })?;
815                let value = client
816                    .upload_app_screenshot(&request.screenshot_set_id, file_name, bytes)
817                    .await
818                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
819                Ok(::rmcp::model::CallToolResult::success(vec![
820                    ::rmcp::model::ContentBlock::json(&value)?,
821                ]))
822            }
823
824            #[::rmcp::tool(
825                name = $app_screenshot_delete_name,
826                title = "Delete a screenshot",
827                annotations(title = "Delete a screenshot", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
828                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."
829            )]
830            async fn app_screenshot_delete(
831                &self,
832                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
833            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
834                #[allow(unused_imports)]
835                use ::smbcloud_ascapi_aso::prelude::*;
836                #[allow(unused_imports)]
837                use ::smbcloud_ascapi_signing::prelude::*;
838                let client = Self::client()?;
839                let request = parameters.0;
840                client
841                    .delete_app_screenshot(&request.id)
842                    .await
843                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
844                Ok(::rmcp::model::CallToolResult::success(vec![
845                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
846                ]))
847            }
848
849            #[::rmcp::tool(
850                name = $certificate_list_name,
851                title = "List signing certificates",
852                annotations(title = "List signing certificates", read_only_hint = true, idempotent_hint = true),
853                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."
854            )]
855            async fn certificate_list(
856                &self,
857                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::CertificateListRequest>,
858            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
859                #[allow(unused_imports)]
860                use ::smbcloud_ascapi_aso::prelude::*;
861                #[allow(unused_imports)]
862                use ::smbcloud_ascapi_signing::prelude::*;
863                let client = Self::client()?;
864                let request = parameters.0;
865                let filter = match request.certificate_type.as_deref() {
866                    Some(value) => Some(
867                        ::smbcloud_ascapi_frontend::certificate_type_from_str(value)
868                            .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?,
869                    ),
870                    None => None,
871                };
872                let mut certificates = client.list_certificates(filter).await.map_err(|error| {
873                    ::rmcp::model::ErrorData::internal_error(error.to_string(), None)
874                })?;
875                certificates.sort_by(|a, b| {
876                    a.attributes.expiration_date.cmp(&b.attributes.expiration_date)
877                });
878                let now = ::smbcloud_ascapi_frontend::now_iso8601();
879                let summaries: Vec<_> = certificates
880                    .iter()
881                    .map(|certificate| ::smbcloud_ascapi_frontend::CertificateSummary::from_resource(certificate, &now))
882                    .collect();
883                Ok(::rmcp::model::CallToolResult::success(vec![
884                    ::rmcp::model::ContentBlock::json(&summaries)?,
885                ]))
886            }
887
888            #[::rmcp::tool(
889                name = $certificate_create_name,
890                title = "Issue a signing certificate",
891                annotations(title = "Issue a signing certificate", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
892                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."
893            )]
894            async fn certificate_create(
895                &self,
896                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::CertificateCreateRequest>,
897            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
898                #[allow(unused_imports)]
899                use ::smbcloud_ascapi_aso::prelude::*;
900                #[allow(unused_imports)]
901                use ::smbcloud_ascapi_signing::prelude::*;
902                let client = Self::client()?;
903                let request = parameters.0;
904                let certificate_type = ::smbcloud_ascapi_frontend::certificate_type_from_str(&request.certificate_type)
905                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
906                let common_name = request
907                    .common_name
908                    .unwrap_or_else(|| "smbcloud-ascapi".to_string());
909                let out_dir = ::std::path::PathBuf::from(&request.out_dir);
910                let outcome = ::smbcloud_ascapi_frontend::issue_certificate(
911                    &client,
912                    certificate_type,
913                    &common_name,
914                    &out_dir,
915                )
916                .await
917                .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
918                Ok(::rmcp::model::CallToolResult::success(vec![
919                    ::rmcp::model::ContentBlock::json(&outcome)?,
920                ]))
921            }
922
923            #[::rmcp::tool(
924                name = $beta_group_list_name,
925                title = "List beta groups",
926                annotations(title = "List beta groups", read_only_hint = true, idempotent_hint = true),
927                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."
928            )]
929            async fn beta_group_list(
930                &self,
931                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BetaGroupListRequest>,
932            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
933                #[allow(unused_imports)]
934                use ::smbcloud_ascapi_aso::prelude::*;
935                let client = Self::client()?;
936                let request = parameters.0;
937                let groups = client
938                    .list_beta_groups(&request.app_id)
939                    .await
940                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
941                Ok(::rmcp::model::CallToolResult::success(vec![
942                    ::rmcp::model::ContentBlock::json(&groups)?,
943                ]))
944            }
945
946            #[::rmcp::tool(
947                name = $beta_app_review_detail_get_name,
948                title = "Get beta app review contact detail",
949                annotations(title = "Get beta app review contact detail", read_only_hint = true, idempotent_hint = true),
950                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."
951            )]
952            async fn beta_app_review_detail_get(
953                &self,
954                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BetaAppReviewDetailGetRequest>,
955            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
956                #[allow(unused_imports)]
957                use ::smbcloud_ascapi_aso::prelude::*;
958                let client = Self::client()?;
959                let request = parameters.0;
960                let detail = client
961                    .get_beta_app_review_detail(&request.app_id)
962                    .await
963                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
964                Ok(::rmcp::model::CallToolResult::success(vec![
965                    ::rmcp::model::ContentBlock::json(&detail)?,
966                ]))
967            }
968        }
969    };
970}