1pub 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}