Skip to main content

smbcloud_ascapi_mcp/
lib.rs

1//! App Store Connect MCP contract.
2//!
3//! Defines the canonical tool set exposed by `asc --mcp`. Each name is
4//! supplied by the embedder through [`crate::ascapi_mcp_tools`], so the
5//! standalone binary and any host that re-exposes this contract share one
6//! implementation and one public surface. Same arrangement `xcrs` uses in
7//! `smbcloud-cli`.
8//!
9//! # Two rules this server is built around
10//!
11//! **stdout belongs to the protocol.** The stdio transport carries
12//! JSON-RPC on stdout, so a stray `println!` anywhere beneath a tool call
13//! corrupts the stream, and the failure surfaces as a parse error in the
14//! client a long way from its cause. Nothing in this path may write to
15//! stdout; diagnostics go to stderr.
16//!
17//! **Tool results are read by a model.** Private keys and raw certificate
18//! bodies never appear in a result; see
19//! [`smbcloud_ascapi_frontend`] for where that is enforced.
20//!
21//! # What is deliberately absent
22//!
23//! Certificate revocation. `asc certificates revoke` exists on the
24//! command line and is intentionally not a tool: revoking a distribution
25//! certificate invalidates every provisioning profile embedding it, for
26//! every teammate and every CI job, at once and irreversibly. That is not
27//! an action a language model should take from a prompt, and no
28//! confirmation string typed by the model rather than the human makes it
29//! safer. Deletes that are scoped and recreatable (a version, a
30//! localization, a screenshot) are exposed, annotated destructive.
31
32pub mod requests;
33pub mod server;
34
35pub use requests::*;
36pub use server::{serve, AscapiMcpServer};
37
38#[macro_export]
39macro_rules! ascapi_mcp_tools {
40    (
41        $server:ty,
42        $app_list_name:literal,
43        $app_get_name:literal,
44        $app_update_name:literal,
45        $app_info_list_name:literal,
46        $build_list_name:literal,
47        $bundle_id_list_name:literal,
48        $bundle_id_create_name:literal,
49        $app_store_version_list_name:literal,
50        $app_store_version_get_name:literal,
51        $app_store_version_create_name:literal,
52        $app_store_version_delete_name:literal,
53        $app_store_version_set_build_name:literal,
54        $app_info_localization_list_name:literal,
55        $app_info_localization_create_name:literal,
56        $app_info_localization_update_name:literal,
57        $app_info_localization_delete_name:literal,
58        $app_store_version_localization_list_name:literal,
59        $app_store_version_localization_create_name:literal,
60        $app_store_version_localization_update_name:literal,
61        $app_store_version_localization_delete_name:literal,
62        $app_screenshot_set_list_name:literal,
63        $app_screenshot_set_create_name:literal,
64        $app_screenshot_set_delete_name:literal,
65        $app_screenshot_list_name:literal,
66        $app_screenshot_upload_name:literal,
67        $app_screenshot_delete_name:literal,
68        $certificate_list_name:literal,
69        $certificate_create_name:literal,
70        $beta_group_list_name:literal,
71        $beta_app_review_detail_get_name:literal
72    ) => {
73        #[::rmcp::tool_router(router = ascapi_tool_router, vis = "pub(crate)")]
74        impl $server {
75            fn client() -> ::std::result::Result<::smbcloud_ascapi_core::Client, ::rmcp::model::ErrorData> {
76                let api_key = ::smbcloud_ascapi_frontend::api_key_from_env()
77                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
78                Ok(::smbcloud_ascapi_core::Client::new(api_key))
79            }
80
81            #[::rmcp::tool(
82                name = $app_list_name,
83                title = "List apps",
84                annotations(title = "List apps", read_only_hint = true, idempotent_hint = true),
85                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."
86            )]
87            async fn app_list(
88                &self,
89                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppListRequest>,
90            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
91                #[allow(unused_imports)]
92                use ::smbcloud_ascapi_aso::prelude::*;
93                #[allow(unused_imports)]
94                use ::smbcloud_ascapi_signing::prelude::*;
95                let client = Self::client()?;
96                let request = parameters.0;
97                let value = client
98                    .list_apps(request.bundle_id.as_deref())
99                    .await
100                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
101                Ok(::rmcp::model::CallToolResult::success(vec![
102                    ::rmcp::model::ContentBlock::json(&value)?,
103                ]))
104            }
105
106            #[::rmcp::tool(
107                name = $app_get_name,
108                title = "Get one app",
109                annotations(title = "Get one app", read_only_hint = true, idempotent_hint = true),
110                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."
111            )]
112            async fn app_get(
113                &self,
114                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppIdRequest>,
115            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
116                #[allow(unused_imports)]
117                use ::smbcloud_ascapi_aso::prelude::*;
118                #[allow(unused_imports)]
119                use ::smbcloud_ascapi_signing::prelude::*;
120                let client = Self::client()?;
121                let request = parameters.0;
122                let value = client
123                    .get_app(&request.app_id)
124                    .await
125                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
126                Ok(::rmcp::model::CallToolResult::success(vec![
127                    ::rmcp::model::ContentBlock::json(&value)?,
128                ]))
129            }
130
131            #[::rmcp::tool(
132                name = $app_update_name,
133                title = "Update app attributes",
134                annotations(title = "Update app attributes", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
135                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."
136            )]
137            async fn app_update(
138                &self,
139                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppUpdateRequest>,
140            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
141                #[allow(unused_imports)]
142                use ::smbcloud_ascapi_aso::prelude::*;
143                #[allow(unused_imports)]
144                use ::smbcloud_ascapi_signing::prelude::*;
145                let client = Self::client()?;
146                let request = parameters.0;
147                let attributes = ::smbcloud_ascapi_aso::app::AppUpdateAttributes {
148                    primary_locale: request.primary_locale,
149                    content_rights_declaration: request.content_rights_declaration,
150                };
151                let value = client
152                    .update_app(&request.app_id, attributes)
153                    .await
154                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
155                Ok(::rmcp::model::CallToolResult::success(vec![
156                    ::rmcp::model::ContentBlock::json(&value)?,
157                ]))
158            }
159
160            #[::rmcp::tool(
161                name = $app_info_list_name,
162                title = "List app infos",
163                annotations(title = "List app infos", read_only_hint = true, idempotent_hint = true),
164                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."
165            )]
166            async fn app_info_list(
167                &self,
168                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppIdRequest>,
169            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
170                #[allow(unused_imports)]
171                use ::smbcloud_ascapi_aso::prelude::*;
172                #[allow(unused_imports)]
173                use ::smbcloud_ascapi_signing::prelude::*;
174                let client = Self::client()?;
175                let request = parameters.0;
176                let value = client
177                    .list_app_infos(&request.app_id)
178                    .await
179                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
180                Ok(::rmcp::model::CallToolResult::success(vec![
181                    ::rmcp::model::ContentBlock::json(&value)?,
182                ]))
183            }
184
185            #[::rmcp::tool(
186                name = $build_list_name,
187                title = "List builds",
188                annotations(title = "List builds", read_only_hint = true, idempotent_hint = true),
189                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."
190            )]
191            async fn build_list(
192                &self,
193                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BuildListRequest>,
194            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
195                #[allow(unused_imports)]
196                use ::smbcloud_ascapi_aso::prelude::*;
197                #[allow(unused_imports)]
198                use ::smbcloud_ascapi_signing::prelude::*;
199                let client = Self::client()?;
200                let request = parameters.0;
201                let filter = ::smbcloud_ascapi_aso::build::BuildFilter {
202                    version: request.version,
203                    pre_release_version: request.pre_release_version,
204                    processing_state: request.processing_state,
205                };
206                let value = client
207                    .list_builds(&request.app_id, &filter)
208                    .await
209                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
210                Ok(::rmcp::model::CallToolResult::success(vec![
211                    ::rmcp::model::ContentBlock::json(&value)?,
212                ]))
213            }
214
215            #[::rmcp::tool(
216                name = $bundle_id_list_name,
217                title = "List bundle IDs",
218                annotations(title = "List bundle IDs", read_only_hint = true, idempotent_hint = true),
219                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."
220            )]
221            async fn bundle_id_list(
222                &self,
223                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BundleIdListRequest>,
224            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
225                #[allow(unused_imports)]
226                use ::smbcloud_ascapi_aso::prelude::*;
227                #[allow(unused_imports)]
228                use ::smbcloud_ascapi_signing::prelude::*;
229                let client = Self::client()?;
230                let request = parameters.0;
231                let value = client
232                    .list_bundle_ids(request.identifier.as_deref())
233                    .await
234                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
235                Ok(::rmcp::model::CallToolResult::success(vec![
236                    ::rmcp::model::ContentBlock::json(&value)?,
237                ]))
238            }
239
240            #[::rmcp::tool(
241                name = $bundle_id_create_name,
242                title = "Register a bundle ID",
243                annotations(title = "Register a bundle ID", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
244                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."
245            )]
246            async fn bundle_id_create(
247                &self,
248                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BundleIdCreateRequest>,
249            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
250                #[allow(unused_imports)]
251                use ::smbcloud_ascapi_aso::prelude::*;
252                #[allow(unused_imports)]
253                use ::smbcloud_ascapi_signing::prelude::*;
254                let client = Self::client()?;
255                let request = parameters.0;
256                let platform = ::smbcloud_ascapi_frontend::bundle_id_platform_from_str(&request.platform)
257                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
258                let attributes = ::smbcloud_ascapi_aso::bundle_id::BundleIdCreateAttributes {
259                    identifier: request.identifier,
260                    name: request.name,
261                    platform,
262                };
263                let value = client
264                    .create_bundle_id(attributes)
265                    .await
266                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
267                Ok(::rmcp::model::CallToolResult::success(vec![
268                    ::rmcp::model::ContentBlock::json(&value)?,
269                ]))
270            }
271
272            #[::rmcp::tool(
273                name = $app_store_version_list_name,
274                title = "List app store versions",
275                annotations(title = "List app store versions", read_only_hint = true, idempotent_hint = true),
276                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."
277            )]
278            async fn app_store_version_list(
279                &self,
280                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppStoreVersionListRequest>,
281            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
282                #[allow(unused_imports)]
283                use ::smbcloud_ascapi_aso::prelude::*;
284                #[allow(unused_imports)]
285                use ::smbcloud_ascapi_signing::prelude::*;
286                let client = Self::client()?;
287                let request = parameters.0;
288                let platform = match request.platform.as_deref() {
289                    Some(value) => Some(
290                        ::smbcloud_ascapi_frontend::platform_from_str(value)
291                            .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?,
292                    ),
293                    None => None,
294                };
295                let value = client
296                    .list_app_store_versions(&request.app_id, platform)
297                    .await
298                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
299                Ok(::rmcp::model::CallToolResult::success(vec![
300                    ::rmcp::model::ContentBlock::json(&value)?,
301                ]))
302            }
303
304            #[::rmcp::tool(
305                name = $app_store_version_get_name,
306                title = "Get one app store version",
307                annotations(title = "Get one app store version", read_only_hint = true, idempotent_hint = true),
308                description = "Purpose: fetch one App Store Version's attributes by id. When to use vs siblings: use app_store_version_list when you have an app id rather than a version id. Behavior: returns the single version resource with its platform, version string, and release state. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: attributes only; its localizations and attached build are fetched with their own tools."
309            )]
310            async fn app_store_version_get(
311                &self,
312                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
313            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
314                #[allow(unused_imports)]
315                use ::smbcloud_ascapi_aso::prelude::*;
316                #[allow(unused_imports)]
317                use ::smbcloud_ascapi_signing::prelude::*;
318                let client = Self::client()?;
319                let request = parameters.0;
320                let value = client
321                    .get_app_store_version(&request.id)
322                    .await
323                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
324                Ok(::rmcp::model::CallToolResult::success(vec![
325                    ::rmcp::model::ContentBlock::json(&value)?,
326                ]))
327            }
328
329            #[::rmcp::tool(
330                name = $app_store_version_create_name,
331                title = "Create an app store version",
332                annotations(title = "Create an app store version", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
333                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."
334            )]
335            async fn app_store_version_create(
336                &self,
337                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppStoreVersionCreateRequest>,
338            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
339                #[allow(unused_imports)]
340                use ::smbcloud_ascapi_aso::prelude::*;
341                #[allow(unused_imports)]
342                use ::smbcloud_ascapi_signing::prelude::*;
343                let client = Self::client()?;
344                let request = parameters.0;
345                let platform = ::smbcloud_ascapi_frontend::platform_from_str(&request.platform)
346                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
347                let attributes = ::smbcloud_ascapi_aso::app_store_version::AppStoreVersionCreateAttributes {
348                    platform,
349                    version_string: request.version_string,
350                    copyright: request.copyright,
351                };
352                let value = client
353                    .create_app_store_version(&request.app_id, attributes)
354                    .await
355                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
356                Ok(::rmcp::model::CallToolResult::success(vec![
357                    ::rmcp::model::ContentBlock::json(&value)?,
358                ]))
359            }
360
361            #[::rmcp::tool(
362                name = $app_store_version_delete_name,
363                title = "Delete an app store version",
364                annotations(title = "Delete an app store version", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
365                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."
366            )]
367            async fn app_store_version_delete(
368                &self,
369                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
370            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
371                #[allow(unused_imports)]
372                use ::smbcloud_ascapi_aso::prelude::*;
373                #[allow(unused_imports)]
374                use ::smbcloud_ascapi_signing::prelude::*;
375                let client = Self::client()?;
376                let request = parameters.0;
377                client
378                    .delete_app_store_version(&request.id)
379                    .await
380                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
381                Ok(::rmcp::model::CallToolResult::success(vec![
382                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
383                ]))
384            }
385
386            #[::rmcp::tool(
387                name = $app_store_version_set_build_name,
388                title = "Attach a build to a version",
389                annotations(title = "Attach a build to a version", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
390                description = "Purpose: attach an uploaded build to an App Store Version, which a version needs before it can be submitted. When to use vs siblings: get the build id from build_list and the version id from app_store_version_list. Behavior: points the version's build relationship at the given build, replacing whatever was attached. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: the build must have finished processing, and its platform must match the version's; Apple rejects the pairing otherwise."
391            )]
392            async fn app_store_version_set_build(
393                &self,
394                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::SetBuildRequest>,
395            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
396                #[allow(unused_imports)]
397                use ::smbcloud_ascapi_aso::prelude::*;
398                #[allow(unused_imports)]
399                use ::smbcloud_ascapi_signing::prelude::*;
400                let client = Self::client()?;
401                let request = parameters.0;
402                client
403                    .set_app_store_version_build(&request.version_id, &request.build_id)
404                    .await
405                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
406                Ok(::rmcp::model::CallToolResult::success(vec![
407                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "versionId": request.version_id, "buildId": request.build_id }))?,
408                ]))
409            }
410
411            #[::rmcp::tool(
412                name = $app_info_localization_list_name,
413                title = "List app info localizations",
414                annotations(title = "List app info localizations", read_only_hint = true, idempotent_hint = true),
415                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."
416            )]
417            async fn app_info_localization_list(
418                &self,
419                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationListRequest>,
420            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
421                #[allow(unused_imports)]
422                use ::smbcloud_ascapi_aso::prelude::*;
423                #[allow(unused_imports)]
424                use ::smbcloud_ascapi_signing::prelude::*;
425                let client = Self::client()?;
426                let request = parameters.0;
427                let value = client
428                    .list_app_info_localizations(&request.app_info_id)
429                    .await
430                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
431                Ok(::rmcp::model::CallToolResult::success(vec![
432                    ::rmcp::model::ContentBlock::json(&value)?,
433                ]))
434            }
435
436            #[::rmcp::tool(
437                name = $app_info_localization_create_name,
438                title = "Add an app info localization",
439                annotations(title = "Add an app info localization", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
440                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."
441            )]
442            async fn app_info_localization_create(
443                &self,
444                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationCreateRequest>,
445            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
446                #[allow(unused_imports)]
447                use ::smbcloud_ascapi_aso::prelude::*;
448                #[allow(unused_imports)]
449                use ::smbcloud_ascapi_signing::prelude::*;
450                let client = Self::client()?;
451                let request = parameters.0;
452                let attributes = ::smbcloud_ascapi_aso::app_info_localization::AppInfoLocalizationCreateAttributes {
453                    locale: request.locale,
454                    name: request.name,
455                    subtitle: request.subtitle,
456                    privacy_policy_url: request.privacy_policy_url,
457                };
458                let value = client
459                    .create_app_info_localization(&request.app_info_id, attributes)
460                    .await
461                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
462                Ok(::rmcp::model::CallToolResult::success(vec![
463                    ::rmcp::model::ContentBlock::json(&value)?,
464                ]))
465            }
466
467            #[::rmcp::tool(
468                name = $app_info_localization_update_name,
469                title = "Edit an app info localization",
470                annotations(title = "Edit an app info localization", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
471                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."
472            )]
473            async fn app_info_localization_update(
474                &self,
475                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::AppInfoLocalizationUpdateRequest>,
476            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
477                #[allow(unused_imports)]
478                use ::smbcloud_ascapi_aso::prelude::*;
479                #[allow(unused_imports)]
480                use ::smbcloud_ascapi_signing::prelude::*;
481                let client = Self::client()?;
482                let request = parameters.0;
483                let attributes = ::smbcloud_ascapi_aso::app_info_localization::AppInfoLocalizationUpdateAttributes {
484                    name: request.name,
485                    subtitle: request.subtitle,
486                    privacy_policy_url: request.privacy_policy_url,
487                };
488                let value = client
489                    .update_app_info_localization(&request.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_delete_name,
499                title = "Delete an app info localization",
500                annotations(title = "Delete an app info localization", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
501                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."
502            )]
503            async fn app_info_localization_delete(
504                &self,
505                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
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                client
514                    .delete_app_info_localization(&request.id)
515                    .await
516                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
517                Ok(::rmcp::model::CallToolResult::success(vec![
518                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
519                ]))
520            }
521
522            #[::rmcp::tool(
523                name = $app_store_version_localization_list_name,
524                title = "List version localizations",
525                annotations(title = "List version localizations", read_only_hint = true, idempotent_hint = true),
526                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."
527            )]
528            async fn app_store_version_localization_list(
529                &self,
530                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationListRequest>,
531            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
532                #[allow(unused_imports)]
533                use ::smbcloud_ascapi_aso::prelude::*;
534                #[allow(unused_imports)]
535                use ::smbcloud_ascapi_signing::prelude::*;
536                let client = Self::client()?;
537                let request = parameters.0;
538                let value = client
539                    .list_app_store_version_localizations(&request.version_id)
540                    .await
541                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
542                Ok(::rmcp::model::CallToolResult::success(vec![
543                    ::rmcp::model::ContentBlock::json(&value)?,
544                ]))
545            }
546
547            #[::rmcp::tool(
548                name = $app_store_version_localization_create_name,
549                title = "Add a version localization",
550                annotations(title = "Add a version localization", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
551                description = "Purpose: add the description, keywords, and release notes for a locale on a specific version. When to use vs siblings: use the update tool when the locale already exists on this version. Behavior: creates the localization with whichever fields are provided and returns it. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: App Store Connect enforces field limits, notably 4000 characters of description, 100 for keywords, and 170 for promotional text, and rejects longer values outright."
552            )]
553            async fn app_store_version_localization_create(
554                &self,
555                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationCreateRequest>,
556            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
557                #[allow(unused_imports)]
558                use ::smbcloud_ascapi_aso::prelude::*;
559                #[allow(unused_imports)]
560                use ::smbcloud_ascapi_signing::prelude::*;
561                let client = Self::client()?;
562                let request = parameters.0;
563                let fields = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationFields {
564                    description: request.description,
565                    keywords: request.keywords,
566                    marketing_url: request.marketing_url,
567                    promotional_text: request.promotional_text,
568                    support_url: request.support_url,
569                    whats_new: request.whats_new,
570                };
571                let attributes = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationCreateAttributes {
572                    locale: request.locale,
573                    fields,
574                };
575                let value = client
576                    .create_app_store_version_localization(&request.version_id, attributes)
577                    .await
578                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
579                Ok(::rmcp::model::CallToolResult::success(vec![
580                    ::rmcp::model::ContentBlock::json(&value)?,
581                ]))
582            }
583
584            #[::rmcp::tool(
585                name = $app_store_version_localization_update_name,
586                title = "Edit a version localization",
587                annotations(title = "Edit a version localization", read_only_hint = false, destructive_hint = false, idempotent_hint = true),
588                description = "Purpose: change the description, keywords, promotional text, or release notes for an existing locale on a version. When to use vs siblings: promotional text is the one field editable without shipping a new build, which makes this the tool for timely copy changes. Behavior: sends only the fields provided and leaves the rest untouched. Prerequisites: ASC_API_KEY, ASC_ISSUER_ID, and a readable .p8 key at ASC_PRIVATE_KEY_PATH or ~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8. Failure modes: missing or unreadable credentials return an invalid-request error naming what is absent; App Store Connect errors are returned verbatim with their status. Limitations: which fields are editable depends on the version's review state, and Apple rejects edits to a version under review."
589            )]
590            async fn app_store_version_localization_update(
591                &self,
592                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::VersionLocalizationUpdateRequest>,
593            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
594                #[allow(unused_imports)]
595                use ::smbcloud_ascapi_aso::prelude::*;
596                #[allow(unused_imports)]
597                use ::smbcloud_ascapi_signing::prelude::*;
598                let client = Self::client()?;
599                let request = parameters.0;
600                let fields = ::smbcloud_ascapi_aso::app_store_version_localization::AppStoreVersionLocalizationFields {
601                    description: request.description,
602                    keywords: request.keywords,
603                    marketing_url: request.marketing_url,
604                    promotional_text: request.promotional_text,
605                    support_url: request.support_url,
606                    whats_new: request.whats_new,
607                };
608                let value = client
609                    .update_app_store_version_localization(&request.id, fields)
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_delete_name,
619                title = "Delete a version localization",
620                annotations(title = "Delete a version localization", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
621                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."
622            )]
623            async fn app_store_version_localization_delete(
624                &self,
625                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
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                client
634                    .delete_app_store_version_localization(&request.id)
635                    .await
636                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
637                Ok(::rmcp::model::CallToolResult::success(vec![
638                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
639                ]))
640            }
641
642            #[::rmcp::tool(
643                name = $app_screenshot_set_list_name,
644                title = "List screenshot sets",
645                annotations(title = "List screenshot sets", read_only_hint = true, idempotent_hint = true),
646                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."
647            )]
648            async fn app_screenshot_set_list(
649                &self,
650                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotSetListRequest>,
651            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
652                #[allow(unused_imports)]
653                use ::smbcloud_ascapi_aso::prelude::*;
654                #[allow(unused_imports)]
655                use ::smbcloud_ascapi_signing::prelude::*;
656                let client = Self::client()?;
657                let request = parameters.0;
658                let value = client
659                    .list_app_screenshot_sets(&request.localization_id)
660                    .await
661                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
662                Ok(::rmcp::model::CallToolResult::success(vec![
663                    ::rmcp::model::ContentBlock::json(&value)?,
664                ]))
665            }
666
667            #[::rmcp::tool(
668                name = $app_screenshot_set_create_name,
669                title = "Create a screenshot set",
670                annotations(title = "Create a screenshot set", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
671                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."
672            )]
673            async fn app_screenshot_set_create(
674                &self,
675                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotSetCreateRequest>,
676            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
677                #[allow(unused_imports)]
678                use ::smbcloud_ascapi_aso::prelude::*;
679                #[allow(unused_imports)]
680                use ::smbcloud_ascapi_signing::prelude::*;
681                let client = Self::client()?;
682                let request = parameters.0;
683                let display_type = ::smbcloud_ascapi_frontend::display_type_from_str(&request.display_type)
684                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
685                let attributes = ::smbcloud_ascapi_aso::app_screenshot_set::AppScreenshotSetCreateAttributes {
686                    screenshot_display_type: display_type,
687                };
688                let value = client
689                    .create_app_screenshot_set(&request.localization_id, attributes)
690                    .await
691                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
692                Ok(::rmcp::model::CallToolResult::success(vec![
693                    ::rmcp::model::ContentBlock::json(&value)?,
694                ]))
695            }
696
697            #[::rmcp::tool(
698                name = $app_screenshot_set_delete_name,
699                title = "Delete a screenshot set",
700                annotations(title = "Delete a screenshot set", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
701                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."
702            )]
703            async fn app_screenshot_set_delete(
704                &self,
705                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
706            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
707                #[allow(unused_imports)]
708                use ::smbcloud_ascapi_aso::prelude::*;
709                #[allow(unused_imports)]
710                use ::smbcloud_ascapi_signing::prelude::*;
711                let client = Self::client()?;
712                let request = parameters.0;
713                client
714                    .delete_app_screenshot_set(&request.id)
715                    .await
716                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
717                Ok(::rmcp::model::CallToolResult::success(vec![
718                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
719                ]))
720            }
721
722            #[::rmcp::tool(
723                name = $app_screenshot_list_name,
724                title = "List screenshots",
725                annotations(title = "List screenshots", read_only_hint = true, idempotent_hint = true),
726                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."
727            )]
728            async fn app_screenshot_list(
729                &self,
730                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotListRequest>,
731            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
732                #[allow(unused_imports)]
733                use ::smbcloud_ascapi_aso::prelude::*;
734                #[allow(unused_imports)]
735                use ::smbcloud_ascapi_signing::prelude::*;
736                let client = Self::client()?;
737                let request = parameters.0;
738                let value = client
739                    .list_app_screenshots(&request.screenshot_set_id)
740                    .await
741                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
742                Ok(::rmcp::model::CallToolResult::success(vec![
743                    ::rmcp::model::ContentBlock::json(&value)?,
744                ]))
745            }
746
747            #[::rmcp::tool(
748                name = $app_screenshot_upload_name,
749                title = "Upload a screenshot",
750                annotations(title = "Upload a screenshot", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
751                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."
752            )]
753            async fn app_screenshot_upload(
754                &self,
755                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::ScreenshotUploadRequest>,
756            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
757                #[allow(unused_imports)]
758                use ::smbcloud_ascapi_aso::prelude::*;
759                #[allow(unused_imports)]
760                use ::smbcloud_ascapi_signing::prelude::*;
761                let client = Self::client()?;
762                let request = parameters.0;
763                let path = ::std::path::PathBuf::from(&request.file_path);
764                let bytes = ::std::fs::read(&path).map_err(|error| {
765                    ::rmcp::model::ErrorData::invalid_request(
766                        format!("reading {}: {error}", path.display()),
767                        None,
768                    )
769                })?;
770                let file_name = path
771                    .file_name()
772                    .map(|name| name.to_string_lossy().into_owned())
773                    .ok_or_else(|| {
774                        ::rmcp::model::ErrorData::invalid_request(
775                            format!("{} has no file name", path.display()),
776                            None,
777                        )
778                    })?;
779                let value = client
780                    .upload_app_screenshot(&request.screenshot_set_id, file_name, bytes)
781                    .await
782                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
783                Ok(::rmcp::model::CallToolResult::success(vec![
784                    ::rmcp::model::ContentBlock::json(&value)?,
785                ]))
786            }
787
788            #[::rmcp::tool(
789                name = $app_screenshot_delete_name,
790                title = "Delete a screenshot",
791                annotations(title = "Delete a screenshot", read_only_hint = false, destructive_hint = true, idempotent_hint = true),
792                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."
793            )]
794            async fn app_screenshot_delete(
795                &self,
796                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::IdRequest>,
797            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
798                #[allow(unused_imports)]
799                use ::smbcloud_ascapi_aso::prelude::*;
800                #[allow(unused_imports)]
801                use ::smbcloud_ascapi_signing::prelude::*;
802                let client = Self::client()?;
803                let request = parameters.0;
804                client
805                    .delete_app_screenshot(&request.id)
806                    .await
807                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
808                Ok(::rmcp::model::CallToolResult::success(vec![
809                    ::rmcp::model::ContentBlock::json(&::serde_json::json!({ "deleted": request.id }))?,
810                ]))
811            }
812
813            #[::rmcp::tool(
814                name = $certificate_list_name,
815                title = "List signing certificates",
816                annotations(title = "List signing certificates", read_only_hint = true, idempotent_hint = true),
817                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."
818            )]
819            async fn certificate_list(
820                &self,
821                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::CertificateListRequest>,
822            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
823                #[allow(unused_imports)]
824                use ::smbcloud_ascapi_aso::prelude::*;
825                #[allow(unused_imports)]
826                use ::smbcloud_ascapi_signing::prelude::*;
827                let client = Self::client()?;
828                let request = parameters.0;
829                let filter = match request.certificate_type.as_deref() {
830                    Some(value) => Some(
831                        ::smbcloud_ascapi_frontend::certificate_type_from_str(value)
832                            .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?,
833                    ),
834                    None => None,
835                };
836                let mut certificates = client.list_certificates(filter).await.map_err(|error| {
837                    ::rmcp::model::ErrorData::internal_error(error.to_string(), None)
838                })?;
839                certificates.sort_by(|a, b| {
840                    a.attributes.expiration_date.cmp(&b.attributes.expiration_date)
841                });
842                let now = ::smbcloud_ascapi_frontend::now_iso8601();
843                let summaries: Vec<_> = certificates
844                    .iter()
845                    .map(|certificate| ::smbcloud_ascapi_frontend::CertificateSummary::from_resource(certificate, &now))
846                    .collect();
847                Ok(::rmcp::model::CallToolResult::success(vec![
848                    ::rmcp::model::ContentBlock::json(&summaries)?,
849                ]))
850            }
851
852            #[::rmcp::tool(
853                name = $certificate_create_name,
854                title = "Issue a signing certificate",
855                annotations(title = "Issue a signing certificate", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
856                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."
857            )]
858            async fn certificate_create(
859                &self,
860                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::CertificateCreateRequest>,
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 certificate_type = ::smbcloud_ascapi_frontend::certificate_type_from_str(&request.certificate_type)
869                    .map_err(|error| ::rmcp::model::ErrorData::invalid_request(error, None))?;
870                let common_name = request
871                    .common_name
872                    .unwrap_or_else(|| "smbcloud-ascapi".to_string());
873                let out_dir = ::std::path::PathBuf::from(&request.out_dir);
874                let outcome = ::smbcloud_ascapi_frontend::issue_certificate(
875                    &client,
876                    certificate_type,
877                    &common_name,
878                    &out_dir,
879                )
880                .await
881                .map_err(|error| ::rmcp::model::ErrorData::internal_error(error, None))?;
882                Ok(::rmcp::model::CallToolResult::success(vec![
883                    ::rmcp::model::ContentBlock::json(&outcome)?,
884                ]))
885            }
886
887            #[::rmcp::tool(
888                name = $beta_group_list_name,
889                title = "List beta groups",
890                annotations(title = "List beta groups", read_only_hint = true, idempotent_hint = true),
891                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."
892            )]
893            async fn beta_group_list(
894                &self,
895                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BetaGroupListRequest>,
896            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
897                #[allow(unused_imports)]
898                use ::smbcloud_ascapi_aso::prelude::*;
899                let client = Self::client()?;
900                let request = parameters.0;
901                let groups = client
902                    .list_beta_groups(&request.app_id)
903                    .await
904                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
905                Ok(::rmcp::model::CallToolResult::success(vec![
906                    ::rmcp::model::ContentBlock::json(&groups)?,
907                ]))
908            }
909
910            #[::rmcp::tool(
911                name = $beta_app_review_detail_get_name,
912                title = "Get beta app review contact detail",
913                annotations(title = "Get beta app review contact detail", read_only_hint = true, idempotent_hint = true),
914                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."
915            )]
916            async fn beta_app_review_detail_get(
917                &self,
918                parameters: ::rmcp::handler::server::wrapper::Parameters<$crate::BetaAppReviewDetailGetRequest>,
919            ) -> ::std::result::Result<::rmcp::model::CallToolResult, ::rmcp::model::ErrorData> {
920                #[allow(unused_imports)]
921                use ::smbcloud_ascapi_aso::prelude::*;
922                let client = Self::client()?;
923                let request = parameters.0;
924                let detail = client
925                    .get_beta_app_review_detail(&request.app_id)
926                    .await
927                    .map_err(|error| ::rmcp::model::ErrorData::internal_error(error.to_string(), None))?;
928                Ok(::rmcp::model::CallToolResult::success(vec![
929                    ::rmcp::model::ContentBlock::json(&detail)?,
930                ]))
931            }
932        }
933    };
934}