@akoskomuves/appstoreconnect-mcp 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +158 -0
  4. package/data/apple-music-prices.json +78 -0
  5. package/dist/auth.d.ts +9 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +33 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/client.d.ts +6 -0
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +75 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/clients.d.ts +31 -0
  14. package/dist/clients.d.ts.map +1 -0
  15. package/dist/clients.js +88 -0
  16. package/dist/clients.js.map +1 -0
  17. package/dist/config.d.ts +7 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +29 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/digest.d.ts +13 -0
  22. package/dist/digest.d.ts.map +1 -0
  23. package/dist/digest.js +154 -0
  24. package/dist/digest.js.map +1 -0
  25. package/dist/doctor.d.ts +2 -0
  26. package/dist/doctor.d.ts.map +1 -0
  27. package/dist/doctor.js +139 -0
  28. package/dist/doctor.js.map +1 -0
  29. package/dist/domains/apps.d.ts +4 -0
  30. package/dist/domains/apps.d.ts.map +1 -0
  31. package/dist/domains/apps.js +40 -0
  32. package/dist/domains/apps.js.map +1 -0
  33. package/dist/domains/ppp.d.ts +6 -0
  34. package/dist/domains/ppp.d.ts.map +1 -0
  35. package/dist/domains/ppp.js +539 -0
  36. package/dist/domains/ppp.js.map +1 -0
  37. package/dist/domains/pricing.d.ts +4 -0
  38. package/dist/domains/pricing.d.ts.map +1 -0
  39. package/dist/domains/pricing.js +55 -0
  40. package/dist/domains/pricing.js.map +1 -0
  41. package/dist/domains/subscriptions.d.ts +4 -0
  42. package/dist/domains/subscriptions.d.ts.map +1 -0
  43. package/dist/domains/subscriptions.js +84 -0
  44. package/dist/domains/subscriptions.js.map +1 -0
  45. package/dist/domains/territories.d.ts +4 -0
  46. package/dist/domains/territories.d.ts.map +1 -0
  47. package/dist/domains/territories.js +21 -0
  48. package/dist/domains/territories.js.map +1 -0
  49. package/dist/errors.d.ts +6 -0
  50. package/dist/errors.d.ts.map +1 -0
  51. package/dist/errors.js +11 -0
  52. package/dist/errors.js.map +1 -0
  53. package/dist/index.d.ts +3 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +98 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/init.d.ts +6 -0
  58. package/dist/init.d.ts.map +1 -0
  59. package/dist/init.js +316 -0
  60. package/dist/init.js.map +1 -0
  61. package/dist/jsonapi.d.ts +62 -0
  62. package/dist/jsonapi.d.ts.map +1 -0
  63. package/dist/jsonapi.js +78 -0
  64. package/dist/jsonapi.js.map +1 -0
  65. package/dist/ppp/index.d.ts +40 -0
  66. package/dist/ppp/index.d.ts.map +1 -0
  67. package/dist/ppp/index.js +85 -0
  68. package/dist/ppp/index.js.map +1 -0
  69. package/dist/schemas.d.ts +10 -0
  70. package/dist/schemas.d.ts.map +1 -0
  71. package/dist/schemas.js +34 -0
  72. package/dist/schemas.js.map +1 -0
  73. package/package.json +79 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,74 @@
1
+ # appstoreconnect-mcp
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0b98884: `ppp_compute_proposal` and `ppp_apply_proposal`: skip territories where the App Store Connect billing currency differs from the bundled Apple Music index currency. Marked as `currency-mismatch (asc=X, am=Y)` in the proposal table and excluded from the apply set.
8
+
9
+ This guards against a dimensional bug: the Apple Music price ratio is only a valid PPP-FX signal when both numerator (local Apple Music price) and the ASC billing currency match. For USD-billed Gulf markets (BHR, KWT, OMN) where Apple Music is sold in BHD/KWD/OMR, the formula would have produced artificially low prices (~$0.69 instead of ~$1.80 for a $4.99 anchor). Now those markets are explicitly skipped — set them manually via `asc_post_subscription_price` if needed.
10
+
11
+ - eb65a2b: Compact PPP proposal output so the diff table fits in a normal terminal.
12
+
13
+ Apple's `subscriptionPricePoint` IDs are ~50-char base64 strings that blew the proposal table past 80 columns and forced wrap. `ppp_compute_proposal` now shows `POINT_ID` as the last 8 characters of the ID (e.g. `…NjEifQ`) by default; pass `raw: true` to see full IDs. `ppp_apply_proposal`'s elicitation confirmation message drops the `POINT_ID` column entirely — the user is reviewing prices, not relationship IDs, and the server uses the IDs internally regardless.
14
+
15
+ - 175e96d: Handle Apple's 429 rate limits transparently in the HTTP client.
16
+
17
+ `client.request` now retries on 429 up to 6 times, honouring the `Retry-After` header when present and falling back to exponential backoff (2s → 4s → 8s … capped at 60s). Without this, applying many subscription prices in parallel would cause Apple to start rejecting writes after ~50 requests/minute and the per-row catch in `ppp_apply_proposal` was reporting them as failed without retry — leaving partial pending schedules. Discovered when a 60-territory apply only landed 10 of the writes before Apple started throwing 429s.
18
+
19
+ Also lowered the default `maxConcurrency` for `ppp_apply_proposal` from 5 to 2. With the new retry behaviour the higher concurrency mostly produced backoff stalls; 2 keeps writes well under Apple's threshold without sacrificing meaningful wall time on a typical 60-row run.
20
+
21
+ ## 0.1.0
22
+
23
+ ### Minor Changes
24
+
25
+ - a2108dd: Compact responses + auto-pagination across all read tools.
26
+
27
+ Each list/get tool now returns a clean text table by default (`asc_list_apps`, `asc_list_subscription_groups`, `asc_list_subscriptions`, `asc_list_subscription_prices`, `asc_list_subscription_price_points`, `asc_list_territories`, `asc_get_app`). Every tool accepts `raw: true` to get the original JSON:API payload, and paginated tools accept `maxItems` (default 500–1000).
28
+
29
+ Internal changes:
30
+
31
+ - New `paginate()` helper follows `links.next`, merges and dedupes `included` resources across pages.
32
+ - Sparse fieldsets (`fields[type]=…`) applied per tool to avoid pulling unused attributes.
33
+ - `&limit=200` set on all list endpoints (was missing on `subscriptionPrices`, capping responses at 50/175 territories).
34
+ - New `digest.ts` module with one digester per resource type, joining `data` ↔ `included` to surface the actually useful columns (territory + currency + amount instead of relationship URIs).
35
+
36
+ Net effect: a full 175-territory subscription price schedule fits in ~5 KB of text instead of ~90 KB of nested JSON.
37
+
38
+ - cf4afca: Add `init` and `doctor` subcommands.
39
+
40
+ `appstoreconnect-mcp init` is an interactive wizard that opens App Store Connect, copies the `.p8` to `~/.appstore/` with `chmod 600`, prompts for issuer/key IDs (Key ID auto-detected from the filename), verifies auth with a real ASC call, and registers the MCP in any installed clients (Claude Code, Claude Desktop, Cursor, Windsurf — auto-detected).
41
+
42
+ `appstoreconnect-mcp doctor` is a read-only diagnostic — checks key directory permissions, parses each `.p8`, lists registered clients, and optionally hits the live API if env vars are set.
43
+
44
+ The default no-arg invocation continues to start the MCP server over stdio (unchanged behavior for clients).
45
+
46
+ - b0794b6: Add `ppp_apply_proposal` — the single-tool entry point for a PPP rebalance.
47
+
48
+ Computes the same proposal as `ppp_compute_proposal`, then asks the user to confirm via MCP elicitation (Claude Code, Claude Desktop, etc.), and on confirm POSTs all eligible price changes against App Store Connect in parallel. The user-facing flow collapses from "propose → review → manually trigger 14 writes → verify" into a single tool call with one in-client confirmation prompt.
49
+
50
+ Built-in guardrails:
51
+
52
+ - `maxDropPct` (default 90%) — refuses to apply if any single row drops more than this; guards against a bad Apple Music index entry crashing a price.
53
+ - `maxConcurrency` (default 5) — parallel POSTs well under Apple's 50/min rate limit.
54
+ - `preserveCurrentPrice: true` by default — existing subscribers are grandfathered.
55
+ - `startDate` defaults to today + 7 days (Apple requires ≥24h; 7 is a safety buffer).
56
+ - `confirm: true` arg as a fallback for clients that don't support elicitation, or for automation.
57
+
58
+ Refactored `src/domains/ppp.ts` to share the proposal-computation logic between `ppp_compute_proposal` and `ppp_apply_proposal`. Added an in-domain `concurrentMap` helper covered by 5 new tests (37 total now passing).
59
+
60
+ - 3f0f600: Add `ppp_load_index` and `ppp_compute_proposal` tools.
61
+
62
+ `ppp_compute_proposal` is the single entry point for a Purchasing Power Parity rebalance: it fetches the current price schedule for the target subscription, loads the bundled Apple Music Individual-plan price snapshot (`data/apple-music-prices.json`), computes a per-territory factor using Apple Music ratios as the implied PPP-FX rate, fetches valid price points in parallel for territories where the target differs from current, snaps to the nearest valid Apple price point (configurable `roundStrategy`: `nearest` / `down` / `up`), and returns a compact dry-run table with `POINT_ID`s ready to feed back into `asc_post_subscription_price`. Apply a configurable sanity floor (`floorFactor`, default 0.15) so bad index data can't crash the price.
63
+
64
+ `ppp_load_index` returns the bundled snapshot as a sorted table (or raw JSON) so users can see what reference data is in play.
65
+
66
+ The bundled snapshot covers ~70 territories with high-confidence Apple Music prices as of 2026-05-09; refresh upstream by editing `data/apple-music-prices.json` and resubmitting.
67
+
68
+ Updated `examples/ppp-rebalance/SKILL.md` to drive the new tool. The skill now does discover → propose → review → apply → verify → rollback, with the gotchas (preserveCurrentPrice, Russia, USD-only territories, snap direction) called out.
69
+
70
+ ### Patch Changes
71
+
72
+ - d4326ce: `init`: scan `~/.appstore`, `~/Downloads`, and `~/Desktop` for `.p8` files and present them as a select list (sorted most-recent-first, with auto-detected Key IDs). Falls back to manual path entry. Avoids the chore of typing a 60-character path.
73
+
74
+ This changelog is generated from [changesets](.changeset/). See [CONTRIBUTING.md](CONTRIBUTING.md#working-on-a-change).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Akos Komuves
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # appstoreconnect-mcp
2
+
3
+ [![npm](https://img.shields.io/npm/v/@akoskomuves/appstoreconnect-mcp.svg)](https://www.npmjs.com/package/@akoskomuves/appstoreconnect-mcp)
4
+ [![CI](https://github.com/akoskomuves/appstoreconnect-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/akoskomuves/appstoreconnect-mcp/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ A [Model Context Protocol](https://modelcontextprotocol.io) server for the [Apple App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi). Drives apps, subscriptions, pricing, and more from any MCP-compatible client (Claude Code, Claude Desktop, Cursor, Windsurf).
8
+
9
+ The first published surface is **subscription pricing** — including a Purchasing Power Parity rebalance flow that's already been used to schedule 120 production price changes across 65 territories on a real iOS app. New ASC domains (TestFlight, sales, screenshots, IAPs) are designed to plug in one file at a time; see [Roadmap](#roadmap).
10
+
11
+ ## Install (zero-config)
12
+
13
+ ```sh
14
+ npx @akoskomuves/appstoreconnect-mcp init
15
+ ```
16
+
17
+ The wizard:
18
+
19
+ 1. Opens [App Store Connect → Keys](https://appstoreconnect.apple.com/access/integrations/api) so you can download a `.p8` (skipped if you already have one).
20
+ 2. Copies the key to `~/.appstore/` with `chmod 600`.
21
+ 3. Asks for your Issuer ID and (auto-detected) Key ID.
22
+ 4. Verifies auth with a real API call before writing anything.
23
+ 5. Detects which MCP clients you have installed — Claude Code, Claude Desktop, Cursor, Windsurf — and registers itself in the ones you pick.
24
+
25
+ When something looks off later, run a read-only diagnostic:
26
+
27
+ ```sh
28
+ npx @akoskomuves/appstoreconnect-mcp doctor
29
+ ```
30
+
31
+ ### Manual install
32
+
33
+ If you'd rather wire it up by hand, add to `~/.claude.json` (Claude Code), `claude_desktop_config.json` (Claude Desktop), or your client's equivalent:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "appstoreconnect": {
39
+ "command": "npx",
40
+ "args": ["-y", "@akoskomuves/appstoreconnect-mcp"],
41
+ "env": {
42
+ "ASC_ISSUER_ID": "...",
43
+ "ASC_KEY_ID": "...",
44
+ "ASC_PRIVATE_KEY_PATH": "~/.appstore/AuthKey_XXXXXXXXXX.p8"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ Or via Claude Code's CLI:
52
+
53
+ ```sh
54
+ claude mcp add appstoreconnect \
55
+ -e ASC_ISSUER_ID=... \
56
+ -e ASC_KEY_ID=... \
57
+ -e ASC_PRIVATE_KEY_PATH=~/.appstore/AuthKey_XXXXXXXXXX.p8 \
58
+ -- npx -y @akoskomuves/appstoreconnect-mcp
59
+ ```
60
+
61
+ ## Configure
62
+
63
+ Generate an App Store Connect API key at [App Store Connect → Users and Access → Integrations → Keys](https://appstoreconnect.apple.com/access/integrations/api). Pricing writes need the **Admin** role; read-only operations work with **App Manager**.
64
+
65
+ | Variable | What |
66
+ | --- | --- |
67
+ | `ASC_ISSUER_ID` | Issuer UUID from the Keys page |
68
+ | `ASC_KEY_ID` | 10-character Key ID |
69
+ | `ASC_PRIVATE_KEY_PATH` | Path to your downloaded `AuthKey_XXXXXXXXXX.p8` file (`~` is expanded) |
70
+
71
+ The `.p8` file is a private key — never commit it. Recommended: `~/.appstore/AuthKey_XXXXXXXXXX.p8` outside any repo.
72
+
73
+ ## Tools
74
+
75
+ ### Apps
76
+ - `asc_list_apps` — list apps (filter by `bundleId`)
77
+ - `asc_get_app` — fetch one app by ID
78
+
79
+ ### Subscriptions
80
+ - `asc_list_subscription_groups` — groups for an app
81
+ - `asc_list_subscriptions` — auto-renewable subscriptions in a group
82
+ - `asc_list_subscription_prices` — current price schedule per subscription
83
+ - `asc_list_subscription_price_points` — valid price points for a subscription in a territory
84
+
85
+ ### Pricing (writes)
86
+ - `asc_post_subscription_price` — schedule a price change for one territory
87
+ - `asc_delete_subscription_price` — cancel a pending scheduled change
88
+
89
+ ### Territories
90
+ - `asc_list_territories` — all 175 App Store territories
91
+
92
+ ### PPP rebalancing
93
+ - `ppp_load_index` — return the bundled Apple Music Individual-plan price snapshot used as the PPP signal
94
+ - `ppp_compute_proposal` — compute a proposed per-territory price schedule for a subscription (read-only dry-run; uses Apple Music ratios as implied PPP-FX, snaps to valid Apple price points, applies a configurable round strategy and floor)
95
+ - `ppp_apply_proposal` — same computation, then schedule the changes against ASC after confirming via MCP elicitation (or `confirm: true` for unattended use). Refuses to apply if any row drops by more than `maxDropPct` (default 90%); paces writes at `maxConcurrency` (default 2) and retries 429s automatically; skips territories where ASC billing currency ≠ Apple Music currency.
96
+
97
+ ### Response shape
98
+
99
+ Every list/get tool returns a compact text table by default — designed for an LLM to read without burning context. Every tool also accepts:
100
+
101
+ - `raw: true` — return the full JSON:API payload (`data`, `included`, `links`, `meta`) for debugging or advanced use.
102
+ - `maxItems: number` — cap auto-pagination (default 500–1000 depending on the tool). The MCP follows `links.next` and merges + dedupes `included` resources across pages.
103
+
104
+ Sparse fieldsets (`fields[type]=...`) are applied per tool to avoid pulling unused attributes. The whole 175-territory price schedule comes back in one paginated call (200/page) at roughly 1/10th the size of the unfiltered payload.
105
+
106
+ ## Production behavior
107
+
108
+ A few details worth knowing before running `ppp_apply_proposal` against a live App Store Connect account:
109
+
110
+ - **Rate limit handling.** Apple throttles POST endpoints around 50/min. `client.request` honours `Retry-After` headers and falls back to exponential backoff (2s → 60s, capped, up to 6 retries). A 60-territory rebalance pacing through retries finishes in about 2 minutes wall time with zero manual intervention.
111
+ - **Currency-mismatch skip.** If the bundled Apple Music index lists a territory in one currency (say BHD) but ASC bills your subscription in another (USD), the PPP-FX ratio breaks dimensionally. The proposal marks those rows `currency-mismatch (asc=USD, am=BHD)` and excludes them from the apply set. Common in Gulf USD-billed markets (BHR, KWT, OMN). Set those manually if you want to.
112
+ - **Sanity floor.** `floorFactor` (default 0.15) is a hard lower bound on per-territory drops as a fraction of the current price — guards against a stale index entry collapsing a price to near-zero. For a more conservative rebalance, pass 0.30 or 0.50.
113
+ - **Sanity ceiling on drops.** `maxDropPct` (default 90%) refuses to apply *any* run where a single row drops more than this. If you've ever seen Apple Music tank a market price aggressively, this catches the resulting outlier before you write it to ASC.
114
+ - **Refresh the snapshot when you care.** `data/apple-music-prices.json` is a hand-curated snapshot. Each entry is dated; the snapshot date is shown in proposal output. Pull request a refresh when Apple Music prices move and the project will fold it in.
115
+
116
+ ## PPP rebalancing skill
117
+
118
+ The `examples/ppp-rebalance/` directory contains a [Claude Code skill](https://docs.claude.com/en/docs/claude-code/skills) that wraps these tools into a Purchasing Power Parity workflow (dry-run → schedule → rollback) with the gotchas baked in.
119
+
120
+ ```sh
121
+ mkdir -p ~/.claude/skills && \
122
+ ln -s "$PWD/examples/ppp-rebalance" ~/.claude/skills/ppp-rebalance
123
+ ```
124
+
125
+ Then ask Claude: *"Rebalance my subscription prices using the ppp-rebalance skill."*
126
+
127
+ ## Roadmap
128
+
129
+ - [x] **v0.1** — Apps · subscriptions · subscription pricing · PPP rebalance (with elicitation, 429 retry, currency-mismatch skip, sanity floor/ceiling)
130
+ - [ ] TestFlight (beta groups, testers, builds)
131
+ - [ ] App metadata (screenshots, descriptions, localizations)
132
+ - [ ] Sales reports / analytics
133
+ - [ ] In-app purchases (non-subscription)
134
+ - [ ] Customer reviews
135
+ - [ ] Real-FX support for currency-mismatched territories (today they're skipped; with an FX feed we could compute a correct target)
136
+
137
+ Each new domain is one file under `src/domains/` plus a `register*` call in `src/index.ts`. Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
138
+
139
+ ## Develop
140
+
141
+ ```sh
142
+ git clone https://github.com/akoskomuves/appstoreconnect-mcp.git
143
+ cd appstoreconnect-mcp
144
+ npm install
145
+ npm run dev # tsx watch mode
146
+ npm test
147
+ npm run build
148
+ ```
149
+
150
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the contributor flow (changesets, PR template, branch naming).
151
+
152
+ ## Security
153
+
154
+ Never commit `.p8` keys. Report vulnerabilities privately — see [SECURITY.md](SECURITY.md).
155
+
156
+ ## License
157
+
158
+ [MIT](LICENSE) © 2026 Akos Komuves
@@ -0,0 +1,78 @@
1
+ {
2
+ "$comment": "Apple Music Individual plan price per territory. Used as a Purchasing Power Parity reference: target_local = base_anchor_price * (apple_music_local / apple_music_anchor). Refresh by visiting apple.com/<country-slug>/apple-music/. Territories not listed here are skipped by ppp_compute_proposal.",
3
+ "snapshot": "2026-05-09",
4
+ "anchor": "USA",
5
+ "prices": [
6
+ { "territory": "USA", "currency": "USD", "individualPrice": 10.99 },
7
+ { "territory": "GBR", "currency": "GBP", "individualPrice": 10.99 },
8
+ { "territory": "CAN", "currency": "CAD", "individualPrice": 10.99 },
9
+ { "territory": "AUS", "currency": "AUD", "individualPrice": 12.99 },
10
+ { "territory": "NZL", "currency": "NZD", "individualPrice": 12.99 },
11
+ { "territory": "JPN", "currency": "JPY", "individualPrice": 1080 },
12
+ { "territory": "KOR", "currency": "KRW", "individualPrice": 9900 },
13
+ { "territory": "SGP", "currency": "SGD", "individualPrice": 10.98 },
14
+ { "territory": "HKG", "currency": "HKD", "individualPrice": 78 },
15
+ { "territory": "TWN", "currency": "TWD", "individualPrice": 170 },
16
+ { "territory": "MYS", "currency": "MYR", "individualPrice": 16.9 },
17
+ { "territory": "THA", "currency": "THB", "individualPrice": 129 },
18
+ { "territory": "PHL", "currency": "PHP", "individualPrice": 129 },
19
+ { "territory": "VNM", "currency": "VND", "individualPrice": 65000 },
20
+ { "territory": "IDN", "currency": "IDR", "individualPrice": 59000 },
21
+ { "territory": "IND", "currency": "INR", "individualPrice": 99 },
22
+ { "territory": "PAK", "currency": "PKR", "individualPrice": 360 },
23
+ { "territory": "CHN", "currency": "CNY", "individualPrice": 10 },
24
+
25
+ { "territory": "AUT", "currency": "EUR", "individualPrice": 10.99 },
26
+ { "territory": "BEL", "currency": "EUR", "individualPrice": 10.99 },
27
+ { "territory": "DEU", "currency": "EUR", "individualPrice": 10.99 },
28
+ { "territory": "ESP", "currency": "EUR", "individualPrice": 10.99 },
29
+ { "territory": "FIN", "currency": "EUR", "individualPrice": 10.99 },
30
+ { "territory": "FRA", "currency": "EUR", "individualPrice": 10.99 },
31
+ { "territory": "GRC", "currency": "EUR", "individualPrice": 10.99 },
32
+ { "territory": "IRL", "currency": "EUR", "individualPrice": 10.99 },
33
+ { "territory": "ITA", "currency": "EUR", "individualPrice": 10.99 },
34
+ { "territory": "LUX", "currency": "EUR", "individualPrice": 10.99 },
35
+ { "territory": "NLD", "currency": "EUR", "individualPrice": 10.99 },
36
+ { "territory": "PRT", "currency": "EUR", "individualPrice": 10.99 },
37
+ { "territory": "EST", "currency": "EUR", "individualPrice": 10.99 },
38
+ { "territory": "LVA", "currency": "EUR", "individualPrice": 10.99 },
39
+ { "territory": "LTU", "currency": "EUR", "individualPrice": 10.99 },
40
+ { "territory": "SVN", "currency": "EUR", "individualPrice": 10.99 },
41
+ { "territory": "SVK", "currency": "EUR", "individualPrice": 10.99 },
42
+ { "territory": "CYP", "currency": "EUR", "individualPrice": 10.99 },
43
+ { "territory": "MLT", "currency": "EUR", "individualPrice": 10.99 },
44
+ { "territory": "HRV", "currency": "EUR", "individualPrice": 10.99 },
45
+ { "territory": "BGR", "currency": "BGN", "individualPrice": 19.99 },
46
+ { "territory": "POL", "currency": "PLN", "individualPrice": 24.99 },
47
+ { "territory": "ROU", "currency": "RON", "individualPrice": 24.99 },
48
+ { "territory": "HUN", "currency": "HUF", "individualPrice": 1490 },
49
+ { "territory": "CZE", "currency": "CZK", "individualPrice": 169 },
50
+ { "territory": "DNK", "currency": "DKK", "individualPrice": 79 },
51
+ { "territory": "SWE", "currency": "SEK", "individualPrice": 119 },
52
+ { "territory": "NOR", "currency": "NOK", "individualPrice": 109 },
53
+ { "territory": "ISL", "currency": "USD", "individualPrice": 10.99 },
54
+ { "territory": "CHE", "currency": "CHF", "individualPrice": 12.95 },
55
+ { "territory": "TUR", "currency": "TRY", "individualPrice": 36.99 },
56
+ { "territory": "ISR", "currency": "ILS", "individualPrice": 19.9 },
57
+
58
+ { "territory": "BRA", "currency": "BRL", "individualPrice": 21.9 },
59
+ { "territory": "MEX", "currency": "MXN", "individualPrice": 115 },
60
+ { "territory": "ARG", "currency": "USD", "individualPrice": 10.99 },
61
+ { "territory": "CHL", "currency": "CLP", "individualPrice": 4990 },
62
+ { "territory": "COL", "currency": "COP", "individualPrice": 14000 },
63
+ { "territory": "PER", "currency": "PEN", "individualPrice": 17.9 },
64
+
65
+ { "territory": "ZAF", "currency": "ZAR", "individualPrice": 64.99 },
66
+ { "territory": "EGY", "currency": "EGP", "individualPrice": 89 },
67
+ { "territory": "NGA", "currency": "NGN", "individualPrice": 900 },
68
+ { "territory": "KEN", "currency": "KES", "individualPrice": 250 },
69
+ { "territory": "ARE", "currency": "AED", "individualPrice": 19.99 },
70
+ { "territory": "SAU", "currency": "SAR", "individualPrice": 19.99 },
71
+ { "territory": "QAT", "currency": "QAR", "individualPrice": 19.99 },
72
+ { "territory": "KWT", "currency": "KWD", "individualPrice": 1.49 },
73
+ { "territory": "BHR", "currency": "BHD", "individualPrice": 1.49 },
74
+ { "territory": "OMN", "currency": "OMR", "individualPrice": 1.99 },
75
+ { "territory": "JOR", "currency": "USD", "individualPrice": 4.99 },
76
+ { "territory": "LBN", "currency": "USD", "individualPrice": 4.99 }
77
+ ]
78
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { Config } from './config.js';
2
+ export declare class TokenProvider {
3
+ private readonly config;
4
+ private cache;
5
+ constructor(config: Config);
6
+ getToken(): Promise<string>;
7
+ invalidate(): void;
8
+ }
9
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAY1C,qBAAa,aAAa;IAGZ,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,KAAK,CAA4B;gBAEZ,MAAM,EAAE,MAAM;IAErC,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAkBjC,UAAU,IAAI,IAAI;CAGnB"}
package/dist/auth.js ADDED
@@ -0,0 +1,33 @@
1
+ import { importPKCS8, SignJWT } from 'jose';
2
+ const ASC_AUDIENCE = 'appstoreconnect-v1';
3
+ // Apple caps token lifetime at 20 min; use 19 to leave buffer for clock skew.
4
+ const TOKEN_TTL_SECONDS = 60 * 19;
5
+ const REFRESH_AHEAD_SECONDS = 30;
6
+ export class TokenProvider {
7
+ config;
8
+ cache = null;
9
+ constructor(config) {
10
+ this.config = config;
11
+ }
12
+ async getToken() {
13
+ const now = Math.floor(Date.now() / 1000);
14
+ if (this.cache && this.cache.expiresAt > now + REFRESH_AHEAD_SECONDS) {
15
+ return this.cache.jwt;
16
+ }
17
+ const key = await importPKCS8(this.config.privateKey, 'ES256');
18
+ const expiresAt = now + TOKEN_TTL_SECONDS;
19
+ const jwt = await new SignJWT({})
20
+ .setProtectedHeader({ alg: 'ES256', kid: this.config.keyId, typ: 'JWT' })
21
+ .setIssuer(this.config.issuerId)
22
+ .setAudience(ASC_AUDIENCE)
23
+ .setIssuedAt(now)
24
+ .setExpirationTime(expiresAt)
25
+ .sign(key);
26
+ this.cache = { jwt, expiresAt };
27
+ return jwt;
28
+ }
29
+ invalidate() {
30
+ this.cache = null;
31
+ }
32
+ }
33
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAG5C,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAC1C,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,EAAE,GAAG,EAAE,CAAC;AAClC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAOjC,MAAM,OAAO,aAAa;IAGK;IAFrB,KAAK,GAAuB,IAAI,CAAC;IAEzC,YAA6B,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAE/C,KAAK,CAAC,QAAQ;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,GAAG,qBAAqB,EAAE,CAAC;YACrE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;QACxB,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC/D,MAAM,SAAS,GAAG,GAAG,GAAG,iBAAiB,CAAC;QAC1C,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,EAAE,CAAC;aAC9B,kBAAkB,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;aACxE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;aAC/B,WAAW,CAAC,YAAY,CAAC;aACzB,WAAW,CAAC,GAAG,CAAC;aAChB,iBAAiB,CAAC,SAAS,CAAC;aAC5B,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;QAChC,OAAO,GAAG,CAAC;IACb,CAAC;IAED,UAAU;QACR,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ import type { Config } from './config.js';
2
+ export interface ASCClient {
3
+ request<T>(path: string, init?: RequestInit): Promise<T>;
4
+ }
5
+ export declare function createASCClient(config: Config): ASCClient;
6
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQ1C,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC1D;AAuBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAwDzD"}
package/dist/client.js ADDED
@@ -0,0 +1,75 @@
1
+ import { TokenProvider } from './auth.js';
2
+ import { ASCError } from './errors.js';
3
+ const ASC_BASE_URL = 'https://api.appstoreconnect.apple.com';
4
+ const MAX_RATE_LIMIT_RETRIES = 6;
5
+ const RATE_LIMIT_FALLBACK_MS = 2000;
6
+ const RATE_LIMIT_MAX_BACKOFF_MS = 60_000;
7
+ /**
8
+ * Parse a Retry-After header value. Apple sometimes returns seconds, sometimes
9
+ * an HTTP-date. Returns the wait in milliseconds, or undefined if the header
10
+ * is missing/unparseable.
11
+ */
12
+ function parseRetryAfter(value) {
13
+ if (!value)
14
+ return undefined;
15
+ const trimmed = value.trim();
16
+ const seconds = Number(trimmed);
17
+ if (Number.isFinite(seconds) && seconds >= 0)
18
+ return seconds * 1000;
19
+ const date = Date.parse(trimmed);
20
+ if (!Number.isNaN(date))
21
+ return Math.max(0, date - Date.now());
22
+ return undefined;
23
+ }
24
+ function exponentialBackoff(attempt) {
25
+ return Math.min(RATE_LIMIT_MAX_BACKOFF_MS, RATE_LIMIT_FALLBACK_MS * 2 ** attempt);
26
+ }
27
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
28
+ export function createASCClient(config) {
29
+ const tokens = new TokenProvider(config);
30
+ async function send(url, init) {
31
+ const token = await tokens.getToken();
32
+ const headers = new Headers(init.headers);
33
+ headers.set('authorization', `Bearer ${token}`);
34
+ headers.set('accept', 'application/json');
35
+ if (init.body && !headers.has('content-type')) {
36
+ headers.set('content-type', 'application/json');
37
+ }
38
+ return fetch(url, { ...init, headers });
39
+ }
40
+ async function request(path, init = {}) {
41
+ const url = path.startsWith('http') ? path : `${ASC_BASE_URL}${path}`;
42
+ let response = await send(url, init);
43
+ // Auth refresh on 401 (one-shot — no token loop).
44
+ if (response.status === 401) {
45
+ tokens.invalidate();
46
+ response = await send(url, init);
47
+ }
48
+ // Apple's POST endpoints have a global per-minute throttle and 429s are
49
+ // common when applying many subscription prices at once. Honour Retry-After
50
+ // when present, otherwise fall back to exponential backoff.
51
+ let attempt = 0;
52
+ while (response.status === 429 && attempt < MAX_RATE_LIMIT_RETRIES) {
53
+ const retryAfter = parseRetryAfter(response.headers.get('retry-after'));
54
+ const waitMs = retryAfter ?? exponentialBackoff(attempt);
55
+ await sleep(waitMs);
56
+ response = await send(url, init);
57
+ attempt += 1;
58
+ }
59
+ if (!response.ok) {
60
+ let details;
61
+ try {
62
+ details = await response.json();
63
+ }
64
+ catch {
65
+ details = await response.text().catch(() => undefined);
66
+ }
67
+ throw new ASCError(response.status, `App Store Connect API ${response.status} on ${init.method ?? 'GET'} ${path}`, details);
68
+ }
69
+ if (response.status === 204)
70
+ return undefined;
71
+ return (await response.json());
72
+ }
73
+ return { request };
74
+ }
75
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,YAAY,GAAG,uCAAuC,CAAC;AAC7D,MAAM,sBAAsB,GAAG,CAAC,CAAC;AACjC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AACpC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAMzC;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAoB;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,OAAO,GAAG,IAAI,CAAC;IACpE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAe;IACzC,OAAO,IAAI,CAAC,GAAG,CAAC,yBAAyB,EAAE,sBAAsB,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAEnF,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;IAEzC,KAAK,UAAU,IAAI,CAAC,GAAW,EAAE,IAAiB;QAChD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,KAAK,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,UAAU,OAAO,CAAI,IAAY,EAAE,OAAoB,EAAE;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC;QAEtE,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAErC,kDAAkD;QAClD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,CAAC,UAAU,EAAE,CAAC;YACpB,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QAED,wEAAwE;QACxE,4EAA4E;QAC5E,4DAA4D;QAC5D,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,OAAO,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,OAAO,GAAG,sBAAsB,EAAE,CAAC;YACnE,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;YACxE,MAAM,MAAM,GAAG,UAAU,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC;YACzD,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;YACpB,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACjC,OAAO,IAAI,CAAC,CAAC;QACf,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,OAAgB,CAAC;YACrB,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,IAAI,QAAQ,CAChB,QAAQ,CAAC,MAAM,EACf,yBAAyB,QAAQ,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,IAAI,KAAK,IAAI,IAAI,EAAE,EAC7E,OAAO,CACR,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,SAAc,CAAC;QACnD,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;IACtC,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC"}
@@ -0,0 +1,31 @@
1
+ export type ClientId = 'claude-code' | 'claude-desktop' | 'cursor' | 'windsurf';
2
+ export interface ClientDescriptor {
3
+ id: ClientId;
4
+ label: string;
5
+ configPath: string;
6
+ }
7
+ export interface MCPServerEntry {
8
+ command: string;
9
+ args?: string[];
10
+ env?: Record<string, string>;
11
+ }
12
+ export declare function listClients(): ClientDescriptor[];
13
+ export declare function detectClients(): ClientDescriptor[];
14
+ export declare function clientById(id: ClientId): ClientDescriptor;
15
+ export declare function readServer(client: ClientDescriptor, name: string): MCPServerEntry | undefined;
16
+ export declare function upsertServer(client: ClientDescriptor, name: string, entry: MCPServerEntry): void;
17
+ export declare function removeServer(client: ClientDescriptor, name: string): boolean;
18
+ /**
19
+ * Decide what command + args to write into a client config given how this CLI
20
+ * was invoked. If we're running from inside a published `node_modules`, prefer
21
+ * `npx -y appstoreconnect-mcp`. Otherwise (local dev checkout) point at the
22
+ * absolute path of the built `dist/index.js`.
23
+ */
24
+ export declare function resolveServerInvocation(meta: {
25
+ execScriptPath: string;
26
+ pkgName: string;
27
+ }): {
28
+ command: string;
29
+ args: string[];
30
+ };
31
+ //# sourceMappingURL=clients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../src/clients.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,gBAAgB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AA0CD,wBAAgB,WAAW,IAAI,gBAAgB,EAAE,CAEhD;AAED,wBAAgB,aAAa,IAAI,gBAAgB,EAAE,CAElD;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,GAAG,gBAAgB,CAEzD;AAkBD,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAG7F;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI,CAIhG;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAM5E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAC1F,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAMA"}
@@ -0,0 +1,88 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir, platform } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ const HOME = homedir();
5
+ const PLATFORM = platform();
6
+ const CLIENTS = {
7
+ 'claude-code': {
8
+ id: 'claude-code',
9
+ label: 'Claude Code',
10
+ configPath: join(HOME, '.claude.json'),
11
+ },
12
+ 'claude-desktop': {
13
+ id: 'claude-desktop',
14
+ label: 'Claude Desktop',
15
+ configPath: PLATFORM === 'darwin'
16
+ ? join(HOME, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
17
+ : PLATFORM === 'win32'
18
+ ? join(process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json')
19
+ : join(HOME, '.config', 'Claude', 'claude_desktop_config.json'),
20
+ },
21
+ cursor: {
22
+ id: 'cursor',
23
+ label: 'Cursor',
24
+ configPath: join(HOME, '.cursor', 'mcp.json'),
25
+ },
26
+ windsurf: {
27
+ id: 'windsurf',
28
+ label: 'Windsurf',
29
+ configPath: join(HOME, '.codeium', 'windsurf', 'mcp_config.json'),
30
+ },
31
+ };
32
+ export function listClients() {
33
+ return Object.values(CLIENTS);
34
+ }
35
+ export function detectClients() {
36
+ return listClients().filter((c) => existsSync(c.configPath));
37
+ }
38
+ export function clientById(id) {
39
+ return CLIENTS[id];
40
+ }
41
+ function readConfig(path) {
42
+ if (!existsSync(path))
43
+ return {};
44
+ try {
45
+ const raw = readFileSync(path, 'utf-8');
46
+ if (!raw.trim())
47
+ return {};
48
+ return JSON.parse(raw);
49
+ }
50
+ catch (err) {
51
+ throw new Error(`Cannot parse ${path} as JSON: ${err.message}`);
52
+ }
53
+ }
54
+ function writeConfig(path, config) {
55
+ mkdirSync(dirname(path), { recursive: true });
56
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, 'utf-8');
57
+ }
58
+ export function readServer(client, name) {
59
+ const config = readConfig(client.configPath);
60
+ return config.mcpServers?.[name];
61
+ }
62
+ export function upsertServer(client, name, entry) {
63
+ const config = readConfig(client.configPath);
64
+ config.mcpServers = { ...config.mcpServers, [name]: entry };
65
+ writeConfig(client.configPath, config);
66
+ }
67
+ export function removeServer(client, name) {
68
+ const config = readConfig(client.configPath);
69
+ if (!config.mcpServers || !(name in config.mcpServers))
70
+ return false;
71
+ delete config.mcpServers[name];
72
+ writeConfig(client.configPath, config);
73
+ return true;
74
+ }
75
+ /**
76
+ * Decide what command + args to write into a client config given how this CLI
77
+ * was invoked. If we're running from inside a published `node_modules`, prefer
78
+ * `npx -y appstoreconnect-mcp`. Otherwise (local dev checkout) point at the
79
+ * absolute path of the built `dist/index.js`.
80
+ */
81
+ export function resolveServerInvocation(meta) {
82
+ const fromNodeModules = meta.execScriptPath.includes(`/node_modules/${meta.pkgName}/`);
83
+ if (fromNodeModules) {
84
+ return { command: 'npx', args: ['-y', meta.pkgName] };
85
+ }
86
+ return { command: 'node', args: [meta.execScriptPath] };
87
+ }
88
+ //# sourceMappingURL=clients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.js","sourceRoot":"","sources":["../src/clients.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export interface Config {
2
+ issuerId: string;
3
+ keyId: string;
4
+ privateKey: string;
5
+ }
6
+ export declare function loadConfig(): Config;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAcD,wBAAgB,UAAU,IAAI,MAAM,CAanC"}