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