Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Tests/blitz_tests/MCPRegistryCompatibilityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ struct MCPRegistryCompatibilityTests {
#expect(properties["bundleId"] != nil)
}

@Test func ascCreateSubscriptionSupportsExistingGroupId() throws {
let tools = MCPRegistry.allTools()
let createSubscription = try #require(tool(named: "asc_create_subscription", in: tools))
let schema = try #require(createSubscription["inputSchema"] as? [String: Any])
let properties = try #require(schema["properties"] as? [String: Any])
#expect(properties["groupId"] != nil)
#expect((schema["required"] as? [String])?.contains("groupName") == false)
}

@Test func screenshotRegistryUsesDirectTrackTools() {
let tools = MCPRegistry.allTools()
#expect(tool(named: "screenshots_add_asset", in: tools) == nil)
Expand Down
2 changes: 1 addition & 1 deletion docs/iap-subscription-pricing-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ This avoids changing any existing callers.
| Tool | Required Params | Key Enums |
|------|----------------|-----------|
| `asc_create_iap` | productId, name, type, displayName, price | type: CONSUMABLE, NON_CONSUMABLE, NON_RENEWING_SUBSCRIPTION |
| `asc_create_subscription` | groupName, productId, name, displayName, duration, price | duration: ONE_WEEK..ONE_YEAR |
| `asc_create_subscription` | groupId or groupName, productId, name, displayName, duration, price | `groupId` adds to an existing group; duration: ONE_WEEK..ONE_YEAR |
| `asc_set_app_price` | price | — |

**B) Add category mapping** (in `category(for:)` at line ~469):
Expand Down
21 changes: 18 additions & 3 deletions src/managers/asc/ASCMonetizationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,13 @@ extension ASCManager {

// MARK: - Subscription Creation

func createSubscription(groupName: String, name: String, productId: String, displayName: String, description: String?, duration: String, price: String, screenshotPath: String? = nil) {
func createSubscription(groupName: String? = nil, groupId: String? = nil, name: String, productId: String, displayName: String, description: String?, duration: String, price: String, screenshotPath: String? = nil) {
guard let service else { return }
guard let appId = app?.id else { return }
guard groupId != nil || groupName != nil else {
writeError = "A subscription group ID or name is required."
return
}
let startedAt = Date()
writeError = nil
isCreating = true
Expand All @@ -212,15 +216,26 @@ extension ASCManager {
do {
createProgress = 0.03
let group: ASCSubscriptionGroup
if let existing = subscriptionGroups.first(where: { $0.attributes.referenceName == groupName }) {
let groups = try await service.fetchSubscriptionGroups(appId: appId)
if let groupId,
let existing = groups.first(where: { $0.id == groupId }) {
group = existing
} else if let groupName,
let existing = groups.first(where: { $0.attributes.referenceName == groupName }) {
let groupLocs = try await service.fetchSubscriptionGroupLocalizations(groupId: existing.id)
if groupLocs.isEmpty {
try await service.localizeSubscriptionGroup(groupId: existing.id, locale: "en-US", name: groupName)
}
group = existing
} else {
} else if let groupName {
group = try await service.createSubscriptionGroup(appId: appId, referenceName: groupName)
try await service.localizeSubscriptionGroup(groupId: group.id, locale: "en-US", name: groupName)
} else {
throw NSError(
domain: "ASCMonetization",
code: 1,
userInfo: [NSLocalizedDescriptionKey: "Subscription group \(groupId ?? "") was not found."]
)
}

createProgressMessage = "Creating subscription…"
Expand Down
5 changes: 3 additions & 2 deletions src/resources/CLAUDE.md.template
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,12 @@ Create an in-app purchase. Creates the IAP, adds en-US localization, and sets th

### asc_create_subscription

Create an auto-renewable subscription. Creates or reuses a subscription group.
Create an auto-renewable subscription. Pass `groupId` to add it to an existing subscription group, or `groupName` to create/reuse a group by reference name.

| param | type | required | notes |
|---|---|---|---|
| groupName | string | yes | Subscription group name (created if doesn't exist) |
| groupId | string | one of groupId/groupName | Existing App Store Connect subscription group ID; preferred when adding to an existing group |
| groupName | string | one of groupId/groupName | Subscription group reference name (created if it doesn't exist) |
| productId | string | yes | Unique product identifier |
| name | string | yes | Internal reference name |
| displayName | string | yes | User-facing display name (en-US) |
Expand Down
20 changes: 14 additions & 6 deletions src/services/mcp/MCPExecutorASC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1442,15 +1442,19 @@ extension MCPExecutor {
}

func executeASCCreateSubscription(_ args: [String: Any]) async throws -> [String: Any] {
guard let groupName = args["groupName"] as? String,
let productId = args["productId"] as? String,
guard let productId = args["productId"] as? String,
let name = args["name"] as? String,
let displayName = args["displayName"] as? String,
let duration = args["duration"] as? String,
let priceStr = args["price"] as? String,
let screenshotPath = args["screenshotPath"] as? String else {
throw MCPServerService.MCPError.invalidToolArgs
}
let groupId = args["groupId"] as? String
let groupName = args["groupName"] as? String
guard (groupId?.isEmpty == false) || (groupName?.isEmpty == false) else {
return mcpText("Error: provide either groupId for an existing subscription group or groupName for a new/reused group.")
}
let description = args["description"] as? String

let validDurations = ["ONE_WEEK", "ONE_MONTH", "TWO_MONTHS", "THREE_MONTHS", "SIX_MONTHS", "ONE_YEAR"]
Expand All @@ -1463,20 +1467,22 @@ extension MCPExecutor {
await MainActor.run {
var values: [String: String] = [
"kind": "subscription",
"groupName": groupName,
"name": name,
"productId": productId,
"displayName": displayName,
"duration": duration,
"price": priceStr
]
if let groupId, !groupId.isEmpty { values["groupId"] = groupId }
if let groupName, !groupName.isEmpty { values["groupName"] = groupName }
if let description { values["description"] = description }
appState.ascManager.pendingCreateValues = values
}

await MainActor.run {
appState.ascManager.createSubscription(
groupName: groupName,
groupId: groupId,
name: name,
productId: productId,
displayName: displayName,
Expand All @@ -1491,14 +1497,16 @@ extension MCPExecutor {
return mcpText("Error creating subscription: \(error)")
}

return mcpJSON([
var result: [String: Any] = [
"success": true,
"groupName": groupName,
"productId": productId,
"displayName": displayName,
"duration": duration,
"price": priceStr
])
]
if let groupId, !groupId.isEmpty { result["groupId"] = groupId }
if let groupName, !groupName.isEmpty { result["groupName"] = groupName }
return mcpJSON(result)
}

func pollASCCreation() async -> String? {
Expand Down
7 changes: 4 additions & 3 deletions src/services/mcp/MCPRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,10 @@ enum MCPRegistry {

tools.append(tool(
name: "asc_create_subscription",
description: "Create an auto-renewable subscription in App Store Connect. Creates or reuses a subscription group, adds the subscription with en-US localization, sets the price, and uploads a review screenshot. You MUST provide a screenshot — take one of the subscription content in the app simulator first (e.g. the paywall or premium features screen), save to /tmp, then pass the path.",
description: "Create an auto-renewable subscription in App Store Connect. Provide groupId to add it to an existing subscription group, or groupName to create/reuse a group by reference name. Adds en-US localization, sets the price, and uploads a review screenshot. You MUST provide a screenshot — take one of the subscription content in the app simulator first (e.g. the paywall or premium features screen), save to /tmp, then pass the path.",
properties: [
"groupName": ["type": "string", "description": "Subscription group name (created if doesn't exist)"],
"groupId": ["type": "string", "description": "Existing App Store Connect subscription group ID. Use this to add a product to an existing group."],
"groupName": ["type": "string", "description": "Subscription group reference name. Used only when groupId is omitted; creates the group if it does not exist."],
"productId": ["type": "string", "description": "Unique product identifier"],
"name": ["type": "string", "description": "Internal reference name"],
"displayName": ["type": "string", "description": "User-facing display name (en-US localization)"],
Expand All @@ -339,7 +340,7 @@ enum MCPRegistry {
"description": ["type": "string", "description": "User-facing description (optional)"],
"screenshotPath": ["type": "string", "description": "Path to review screenshot (min 640x920px PNG/JPEG). Use blitz-iphone to take a screenshot of the subscription content in the simulator."]
],
required: ["groupName", "productId", "name", "displayName", "duration", "price", "screenshotPath"]
required: ["productId", "name", "displayName", "duration", "price", "screenshotPath"]
))

tools.append(tool(
Expand Down