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