appstore-mcp 1.0.0 → 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Khoa Pham
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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # App Store MCP
2
2
 
3
- A Model Context Protocol (MCP) server for interacting with Apple's App Store iTunes API. This tool enables querying app information, searching the App Store, discovering trending apps, and retrieving app metadata through a standardized interface.
3
+ A Model Context Protocol (MCP) server for interacting with Apple's App Store. This tool enables querying app information, searching the App Store, discovering trending apps, and retrieving app metadata through a standardized interface.
4
4
 
5
5
  ## Features
6
6
 
@@ -29,10 +29,6 @@ A Model Context Protocol (MCP) server for interacting with Apple's App Store iTu
29
29
  - Latest version release date
30
30
  - Current version number
31
31
 
32
- ## Prerequisites
33
-
34
- - Node.js 18.0.0 or higher
35
-
36
32
  ## Installation
37
33
 
38
34
  Add to your MCP config:
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ const tools_1 = require("./mcp/tools");
7
7
  const resources_1 = require("./mcp/resources");
8
8
  const server = new mcp_js_1.McpServer({
9
9
  name: 'appstore-mcp',
10
- version: '1.0.0',
10
+ version: '1.0.1',
11
11
  });
12
12
  (0, tools_1.registerAllTools)(server);
13
13
  (0, resources_1.registerAllResources)(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appstore-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "description": "MCP server for interacting with App Store iTunes API",
6
6
  "main": "dist/index.js",
@@ -17,16 +17,35 @@
17
17
  "lint": "eslint src/**/*.ts",
18
18
  "lint:fix": "eslint src/**/*.ts --fix",
19
19
  "format": "prettier --write \"src/**/*.ts\"",
20
- "format:check": "prettier --check \"src/**/*.ts\""
20
+ "format:check": "prettier --check \"src/**/*.ts\"",
21
+ "prepublishOnly": "pnpm run build"
21
22
  },
22
23
  "keywords": [
23
24
  "mcp",
25
+ "model-context-protocol",
24
26
  "appstore",
25
27
  "itunes",
26
- "api"
28
+ "apple",
29
+ "ios",
30
+ "api",
31
+ "claude",
32
+ "ai"
27
33
  ],
28
- "author": "",
34
+ "author": "Khoa Pham",
29
35
  "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/onmyway133/appstore-mcp.git"
39
+ },
40
+ "homepage": "https://github.com/onmyway133/appstore-mcp#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/onmyway133/appstore-mcp/issues"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "README.md",
47
+ "LICENSE"
48
+ ],
30
49
  "dependencies": {
31
50
  "@modelcontextprotocol/sdk": "^1.25.1",
32
51
  "axios": "^1.7.9",
package/.claude/CLAUDE.md DELETED
@@ -1,29 +0,0 @@
1
- You are senior Nodejs React developer. You write code in well structured, easy to read manner, with self explanatory code.
2
-
3
- Help me implmenet mcp called appstre-mcp that helps to intereact with App Store itunes api
4
-
5
- Use typescript to implement, with modern nodejs version. Using 4 spaces. Use kebab-case for file naming
6
-
7
- Code style and structure whodul be based on /Users/khoa/XcodeProject1/string-catalog-mcp
8
-
9
- The feature code should be based on /Users/khoa/XcodeProject1/indiegoodies/src/features/tools/appstore-search, it uses app store itunes and rss api
10
-
11
- Copy concept and use case, refactor to make code differnt
12
-
13
- so we will have these tools, each tool will be in a folder kinside tools
14
-
15
- src
16
- mcp
17
- tools
18
-
19
- resources
20
-
21
- Use pnpm for packcage management, use lates version of mcp sdk typescript, axios and zod
22
-
23
- Wtih this mcp, yser can ask questiosn like
24
-
25
- - list grossibga pps
26
- - get app detail
27
- - when is this app (name | id) release
28
- - download screenshots for this app
29
- - show trending apps in a categpry ina country. if n country then default to US
@@ -1,24 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm install:*)",
5
- "Bash(pnpm install)",
6
- "Bash(pnpm run build:*)",
7
- "Bash(cat:*)",
8
- "Bash(timeout 3 node:*)",
9
- "Bash(pnpm add:*)",
10
- "Bash(pnpm run typecheck:*)",
11
- "Bash(pnpm run lint)",
12
- "Bash(mkdir:*)",
13
- "Bash(tree:*)",
14
- "Bash(xargs basename:*)",
15
- "Bash(ls:*)",
16
- "Bash(xargs:*)",
17
- "Bash(lsof:*)",
18
- "Bash(git tag:*)",
19
- "Bash(git push:*)",
20
- "Bash(timeout 3 pnpm run dev:*)",
21
- "Bash(gtimeout 3 pnpm run dev:*)"
22
- ]
23
- }
24
- }
package/.prettierrc DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "semi": true,
3
- "singleQuote": true,
4
- "tabWidth": 4,
5
- "trailingComma": "es5",
6
- "printWidth": 100
7
- }
package/eslint.config.js DELETED
@@ -1,9 +0,0 @@
1
- import eslint from '@eslint/js';
2
- import tseslint from 'typescript-eslint';
3
-
4
- export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended, {
5
- rules: {
6
- '@typescript-eslint/no-unused-vars': 'warn',
7
- '@typescript-eslint/no-explicit-any': 'warn',
8
- },
9
- });
package/src/index.ts DELETED
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
- import { registerAllTools } from './mcp/tools';
6
- import { registerAllResources } from './mcp/resources';
7
-
8
- const server = new McpServer({
9
- name: 'appstore-mcp',
10
- version: '1.0.0',
11
- });
12
-
13
- registerAllTools(server);
14
- registerAllResources(server);
15
-
16
- async function main() {
17
- const transport = new StdioServerTransport();
18
- await server.connect(transport);
19
- }
20
-
21
- main().catch((error) => {
22
- console.error('Failed to start server:', error);
23
- process.exit(1);
24
- });
@@ -1,234 +0,0 @@
1
- import { Country } from '../../types';
2
-
3
- export const COUNTRIES: Country[] = [
4
- { code: 'AF', name: 'Afghanistan' },
5
- { code: 'AL', name: 'Albania' },
6
- { code: 'DZ', name: 'Algeria' },
7
- { code: 'AS', name: 'American Samoa' },
8
- { code: 'AD', name: 'Andorra' },
9
- { code: 'AO', name: 'Angola' },
10
- { code: 'AI', name: 'Anguilla' },
11
- { code: 'AR', name: 'Argentina' },
12
- { code: 'AM', name: 'Armenia' },
13
- { code: 'AW', name: 'Aruba' },
14
- { code: 'AU', name: 'Australia' },
15
- { code: 'AT', name: 'Austria' },
16
- { code: 'AZ', name: 'Azerbaijan' },
17
- { code: 'BS', name: 'Bahamas' },
18
- { code: 'BH', name: 'Bahrain' },
19
- { code: 'BD', name: 'Bangladesh' },
20
- { code: 'BB', name: 'Barbados' },
21
- { code: 'BY', name: 'Belarus' },
22
- { code: 'BE', name: 'Belgium' },
23
- { code: 'BZ', name: 'Belize' },
24
- { code: 'BJ', name: 'Benin' },
25
- { code: 'BM', name: 'Bermuda' },
26
- { code: 'BT', name: 'Bhutan' },
27
- { code: 'BO', name: 'Bolivia' },
28
- { code: 'BA', name: 'Bosnia and Herzegovina' },
29
- { code: 'BW', name: 'Botswana' },
30
- { code: 'BR', name: 'Brazil' },
31
- { code: 'VG', name: 'British Virgin Islands' },
32
- { code: 'BN', name: 'Brunei' },
33
- { code: 'BG', name: 'Bulgaria' },
34
- { code: 'BF', name: 'Burkina Faso' },
35
- { code: 'BI', name: 'Burundi' },
36
- { code: 'KH', name: 'Cambodia' },
37
- { code: 'CM', name: 'Cameroon' },
38
- { code: 'CA', name: 'Canada' },
39
- { code: 'CV', name: 'Cape Verde' },
40
- { code: 'KY', name: 'Cayman Islands' },
41
- { code: 'CF', name: 'Central African Republic' },
42
- { code: 'TD', name: 'Chad' },
43
- { code: 'CL', name: 'Chile' },
44
- { code: 'CN', name: 'China' },
45
- { code: 'CO', name: 'Colombia' },
46
- { code: 'KM', name: 'Comoros' },
47
- { code: 'CK', name: 'Cook Islands' },
48
- { code: 'CR', name: 'Costa Rica' },
49
- { code: 'HR', name: 'Croatia' },
50
- { code: 'CU', name: 'Cuba' },
51
- { code: 'CW', name: 'Curacao' },
52
- { code: 'CY', name: 'Cyprus' },
53
- { code: 'CZ', name: 'Czech Republic' },
54
- { code: 'CD', name: 'Democratic Republic of the Congo' },
55
- { code: 'DK', name: 'Denmark' },
56
- { code: 'DJ', name: 'Djibouti' },
57
- { code: 'DM', name: 'Dominica' },
58
- { code: 'DO', name: 'Dominican Republic' },
59
- { code: 'TL', name: 'East Timor' },
60
- { code: 'EC', name: 'Ecuador' },
61
- { code: 'EG', name: 'Egypt' },
62
- { code: 'SV', name: 'El Salvador' },
63
- { code: 'ER', name: 'Eritrea' },
64
- { code: 'EE', name: 'Estonia' },
65
- { code: 'ET', name: 'Ethiopia' },
66
- { code: 'FO', name: 'Faroe Islands' },
67
- { code: 'FJ', name: 'Fiji' },
68
- { code: 'FI', name: 'Finland' },
69
- { code: 'FR', name: 'France' },
70
- { code: 'PF', name: 'French Polynesia' },
71
- { code: 'GA', name: 'Gabon' },
72
- { code: 'GM', name: 'Gambia' },
73
- { code: 'GE', name: 'Georgia' },
74
- { code: 'DE', name: 'Germany' },
75
- { code: 'GH', name: 'Ghana' },
76
- { code: 'GR', name: 'Greece' },
77
- { code: 'GL', name: 'Greenland' },
78
- { code: 'GD', name: 'Grenada' },
79
- { code: 'GU', name: 'Guam' },
80
- { code: 'GT', name: 'Guatemala' },
81
- { code: 'GG', name: 'Guernsey' },
82
- { code: 'GN', name: 'Guinea' },
83
- { code: 'GW', name: 'Guinea-Bissau' },
84
- { code: 'GY', name: 'Guyana' },
85
- { code: 'HT', name: 'Haiti' },
86
- { code: 'HN', name: 'Honduras' },
87
- { code: 'HK', name: 'Hong Kong' },
88
- { code: 'HU', name: 'Hungary' },
89
- { code: 'IS', name: 'Iceland' },
90
- { code: 'IN', name: 'India' },
91
- { code: 'ID', name: 'Indonesia' },
92
- { code: 'IR', name: 'Iran' },
93
- { code: 'IQ', name: 'Iraq' },
94
- { code: 'IE', name: 'Ireland' },
95
- { code: 'IM', name: 'Isle of Man' },
96
- { code: 'IL', name: 'Israel' },
97
- { code: 'IT', name: 'Italy' },
98
- { code: 'CI', name: 'Ivory Coast' },
99
- { code: 'JM', name: 'Jamaica' },
100
- { code: 'JP', name: 'Japan' },
101
- { code: 'JE', name: 'Jersey' },
102
- { code: 'JO', name: 'Jordan' },
103
- { code: 'KZ', name: 'Kazakhstan' },
104
- { code: 'KE', name: 'Kenya' },
105
- { code: 'KI', name: 'Kiribati' },
106
- { code: 'XK', name: 'Kosovo' },
107
- { code: 'KW', name: 'Kuwait' },
108
- { code: 'KG', name: 'Kyrgyzstan' },
109
- { code: 'LA', name: 'Laos' },
110
- { code: 'LV', name: 'Latvia' },
111
- { code: 'LB', name: 'Lebanon' },
112
- { code: 'LS', name: 'Lesotho' },
113
- { code: 'LR', name: 'Liberia' },
114
- { code: 'LY', name: 'Libya' },
115
- { code: 'LI', name: 'Liechtenstein' },
116
- { code: 'LT', name: 'Lithuania' },
117
- { code: 'LU', name: 'Luxembourg' },
118
- { code: 'MO', name: 'Macau' },
119
- { code: 'MK', name: 'Macedonia' },
120
- { code: 'MG', name: 'Madagascar' },
121
- { code: 'MW', name: 'Malawi' },
122
- { code: 'MY', name: 'Malaysia' },
123
- { code: 'MV', name: 'Maldives' },
124
- { code: 'ML', name: 'Mali' },
125
- { code: 'MT', name: 'Malta' },
126
- { code: 'MH', name: 'Marshall Islands' },
127
- { code: 'MR', name: 'Mauritania' },
128
- { code: 'MU', name: 'Mauritius' },
129
- { code: 'YT', name: 'Mayotte' },
130
- { code: 'MX', name: 'Mexico' },
131
- { code: 'FM', name: 'Micronesia' },
132
- { code: 'MD', name: 'Moldova' },
133
- { code: 'MC', name: 'Monaco' },
134
- { code: 'MN', name: 'Mongolia' },
135
- { code: 'ME', name: 'Montenegro' },
136
- { code: 'MA', name: 'Morocco' },
137
- { code: 'MZ', name: 'Mozambique' },
138
- { code: 'MM', name: 'Myanmar' },
139
- { code: 'NA', name: 'Namibia' },
140
- { code: 'NP', name: 'Nepal' },
141
- { code: 'NL', name: 'Netherlands' },
142
- { code: 'AN', name: 'Netherlands Antilles' },
143
- { code: 'NC', name: 'New Caledonia' },
144
- { code: 'NZ', name: 'New Zealand' },
145
- { code: 'NI', name: 'Nicaragua' },
146
- { code: 'NE', name: 'Niger' },
147
- { code: 'NG', name: 'Nigeria' },
148
- { code: 'KP', name: 'North Korea' },
149
- { code: 'MP', name: 'Northern Mariana Islands' },
150
- { code: 'NO', name: 'Norway' },
151
- { code: 'OM', name: 'Oman' },
152
- { code: 'PK', name: 'Pakistan' },
153
- { code: 'PS', name: 'Palestine' },
154
- { code: 'PA', name: 'Panama' },
155
- { code: 'PG', name: 'Papua New Guinea' },
156
- { code: 'PY', name: 'Paraguay' },
157
- { code: 'PE', name: 'Peru' },
158
- { code: 'PH', name: 'Philippines' },
159
- { code: 'PL', name: 'Poland' },
160
- { code: 'PT', name: 'Portugal' },
161
- { code: 'PR', name: 'Puerto Rico' },
162
- { code: 'QA', name: 'Qatar' },
163
- { code: 'CG', name: 'Republic of the Congo' },
164
- { code: 'RE', name: 'Reunion' },
165
- { code: 'RO', name: 'Romania' },
166
- { code: 'RU', name: 'Russia' },
167
- { code: 'RW', name: 'Rwanda' },
168
- { code: 'KN', name: 'Saint Kitts and Nevis' },
169
- { code: 'LC', name: 'Saint Lucia' },
170
- { code: 'MF', name: 'Saint Martin' },
171
- { code: 'PM', name: 'Saint Pierre and Miquelon' },
172
- { code: 'VC', name: 'Saint Vincent and the Grenadines' },
173
- { code: 'WS', name: 'Samoa' },
174
- { code: 'SM', name: 'San Marino' },
175
- { code: 'ST', name: 'Sao Tome and Principe' },
176
- { code: 'SA', name: 'Saudi Arabia' },
177
- { code: 'SN', name: 'Senegal' },
178
- { code: 'RS', name: 'Serbia' },
179
- { code: 'SC', name: 'Seychelles' },
180
- { code: 'SL', name: 'Sierra Leone' },
181
- { code: 'SG', name: 'Singapore' },
182
- { code: 'SX', name: 'Sint Maarten' },
183
- { code: 'SK', name: 'Slovakia' },
184
- { code: 'SI', name: 'Slovenia' },
185
- { code: 'SB', name: 'Solomon Islands' },
186
- { code: 'SO', name: 'Somalia' },
187
- { code: 'ZA', name: 'South Africa' },
188
- { code: 'KR', name: 'South Korea' },
189
- { code: 'SS', name: 'South Sudan' },
190
- { code: 'ES', name: 'Spain' },
191
- { code: 'LK', name: 'Sri Lanka' },
192
- { code: 'SD', name: 'Sudan' },
193
- { code: 'SR', name: 'Suriname' },
194
- { code: 'SZ', name: 'Swaziland' },
195
- { code: 'SE', name: 'Sweden' },
196
- { code: 'CH', name: 'Switzerland' },
197
- { code: 'SY', name: 'Syria' },
198
- { code: 'TW', name: 'Taiwan' },
199
- { code: 'TJ', name: 'Tajikistan' },
200
- { code: 'TZ', name: 'Tanzania' },
201
- { code: 'TH', name: 'Thailand' },
202
- { code: 'TG', name: 'Togo' },
203
- { code: 'TO', name: 'Tonga' },
204
- { code: 'TT', name: 'Trinidad and Tobago' },
205
- { code: 'TN', name: 'Tunisia' },
206
- { code: 'TR', name: 'Turkey' },
207
- { code: 'TM', name: 'Turkmenistan' },
208
- { code: 'TC', name: 'Turks and Caicos Islands' },
209
- { code: 'TV', name: 'Tuvalu' },
210
- { code: 'VI', name: 'U.S. Virgin Islands' },
211
- { code: 'UG', name: 'Uganda' },
212
- { code: 'UA', name: 'Ukraine' },
213
- { code: 'AE', name: 'United Arab Emirates' },
214
- { code: 'GB', name: 'United Kingdom' },
215
- { code: 'US', name: 'United States' },
216
- { code: 'UY', name: 'Uruguay' },
217
- { code: 'UZ', name: 'Uzbekistan' },
218
- { code: 'VU', name: 'Vanuatu' },
219
- { code: 'VE', name: 'Venezuela' },
220
- { code: 'VN', name: 'Vietnam' },
221
- { code: 'EH', name: 'Western Sahara' },
222
- { code: 'YE', name: 'Yemen' },
223
- { code: 'ZM', name: 'Zambia' },
224
- { code: 'ZW', name: 'Zimbabwe' },
225
- ];
226
-
227
- export function findCountryByCode(code: string): Country | undefined {
228
- return COUNTRIES.find((country) => country.code.toUpperCase() === code.toUpperCase());
229
- }
230
-
231
- export function findCountryByName(name: string): Country | undefined {
232
- const lowerName = name.toLowerCase();
233
- return COUNTRIES.find((country) => country.name.toLowerCase().includes(lowerName));
234
- }
@@ -1,117 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { COUNTRIES } from './countries';
3
- import { APP_STORE_CATEGORIES, PLATFORMS, PRICING_TYPES } from '../../types';
4
-
5
- export function registerAllResources(server: McpServer) {
6
- server.registerResource(
7
- 'countries',
8
- 'appstore://countries',
9
- {
10
- description: 'List of all available App Store country codes',
11
- mimeType: 'application/json',
12
- },
13
- async (uri) => ({
14
- contents: [
15
- {
16
- uri: uri.href,
17
- mimeType: 'application/json',
18
- text: JSON.stringify(
19
- {
20
- count: COUNTRIES.length,
21
- countries: COUNTRIES.map((country) => ({
22
- code: country.code,
23
- name: country.name,
24
- })),
25
- },
26
- null,
27
- 2
28
- ),
29
- },
30
- ],
31
- })
32
- );
33
-
34
- server.registerResource(
35
- 'categories',
36
- 'appstore://categories',
37
- {
38
- description: 'List of all App Store categories with their IDs',
39
- mimeType: 'application/json',
40
- },
41
- async (uri) => ({
42
- contents: [
43
- {
44
- uri: uri.href,
45
- mimeType: 'application/json',
46
- text: JSON.stringify(
47
- {
48
- categories: APP_STORE_CATEGORIES.map((category) => ({
49
- id: category.id,
50
- name: category.name,
51
- emoji: category.emoji,
52
- })),
53
- },
54
- null,
55
- 2
56
- ),
57
- },
58
- ],
59
- })
60
- );
61
-
62
- server.registerResource(
63
- 'platforms',
64
- 'appstore://platforms',
65
- {
66
- description: 'List of supported App Store platforms',
67
- mimeType: 'application/json',
68
- },
69
- async (uri) => ({
70
- contents: [
71
- {
72
- uri: uri.href,
73
- mimeType: 'application/json',
74
- text: JSON.stringify(
75
- {
76
- platforms: PLATFORMS.map((platform) => ({
77
- value: platform.value,
78
- label: platform.label,
79
- emoji: platform.emoji,
80
- })),
81
- },
82
- null,
83
- 2
84
- ),
85
- },
86
- ],
87
- })
88
- );
89
-
90
- server.registerResource(
91
- 'pricing-types',
92
- 'appstore://pricing-types',
93
- {
94
- description: 'List of App Store pricing types (free, paid, grossing)',
95
- mimeType: 'application/json',
96
- },
97
- async (uri) => ({
98
- contents: [
99
- {
100
- uri: uri.href,
101
- mimeType: 'application/json',
102
- text: JSON.stringify(
103
- {
104
- pricingTypes: PRICING_TYPES.map((pricing) => ({
105
- value: pricing.value,
106
- label: pricing.label,
107
- emoji: pricing.emoji,
108
- })),
109
- },
110
- null,
111
- 2
112
- ),
113
- },
114
- ],
115
- })
116
- );
117
- }
@@ -1,183 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { z } from 'zod';
3
- import { AppStoreService } from '../../services/app-store-service';
4
-
5
- type InfoType = 'basic' | 'full' | 'release' | 'screenshots';
6
-
7
- function formatDate(dateString?: string): string | null {
8
- if (!dateString) return null;
9
- const date = new Date(dateString);
10
- return date.toLocaleDateString('en-US', {
11
- year: 'numeric',
12
- month: 'long',
13
- day: 'numeric',
14
- });
15
- }
16
-
17
- export function registerGetAppInfo(server: McpServer) {
18
- server.registerTool(
19
- 'get_app_info',
20
- {
21
- description:
22
- 'Get information about a specific app by its App Store ID. Use the "include" parameter to specify what data to return.',
23
- inputSchema: {
24
- appId: z
25
- .string()
26
- .describe('The App Store ID of the app (numeric ID from the App Store URL)'),
27
- country: z
28
- .string()
29
- .optional()
30
- .describe('Country code for the App Store (e.g., US, GB, JP). Defaults to US'),
31
- include: z
32
- .string()
33
- .optional()
34
- .describe(
35
- 'What info to include: "basic" (name, artist, icon, price, rating), "full" (all details including description), "release" (release dates and version), "screenshots" (screenshot URLs). Defaults to "basic"'
36
- ),
37
- },
38
- },
39
- async ({ appId, country, include }) => {
40
- const countryCode = country || 'US';
41
- const infoType = (include || 'basic') as InfoType;
42
-
43
- try {
44
- const app = await AppStoreService.getAppDetails(appId, countryCode);
45
-
46
- if (!app) {
47
- return {
48
- content: [
49
- {
50
- type: 'text' as const,
51
- text: JSON.stringify(
52
- {
53
- success: false,
54
- error: `App with ID ${appId} not found`,
55
- },
56
- null,
57
- 2
58
- ),
59
- },
60
- ],
61
- };
62
- }
63
-
64
- let result: Record<string, unknown> = {
65
- success: true,
66
- appId: app.id,
67
- country: countryCode,
68
- include: infoType,
69
- };
70
-
71
- switch (infoType) {
72
- case 'basic':
73
- result.app = {
74
- id: app.id,
75
- name: app.name,
76
- artistName: app.artistName,
77
- url: app.url,
78
- iconUrl: app.iconUrl,
79
- formattedPrice: app.formattedPrice || 'Free',
80
- rating: app.averageUserRating?.toFixed(1) || 'N/A',
81
- ratingCount: app.userRatingCount || 0,
82
- primaryGenre: app.primaryGenreName,
83
- };
84
- break;
85
-
86
- case 'full':
87
- result.app = {
88
- id: app.id,
89
- name: app.name,
90
- artistName: app.artistName,
91
- bundleId: app.bundleId,
92
- url: app.url,
93
- iconUrl: app.iconUrl,
94
- description: app.description,
95
- version: app.version,
96
- price: app.price,
97
- formattedPrice: app.formattedPrice || 'Free',
98
- primaryGenre: app.primaryGenreName,
99
- genres: app.genres,
100
- rating: app.averageUserRating?.toFixed(1) || 'N/A',
101
- ratingCount: app.userRatingCount || 0,
102
- releaseDate: app.releaseDate,
103
- releaseDateFormatted: formatDate(app.releaseDate),
104
- currentVersionReleaseDate: app.currentVersionReleaseDate,
105
- currentVersionReleaseDateFormatted: formatDate(
106
- app.currentVersionReleaseDate
107
- ),
108
- minimumOsVersion: app.minimumOsVersion,
109
- fileSizeBytes: app.fileSizeBytes,
110
- contentAdvisoryRating: app.contentAdvisoryRating,
111
- screenshotCount: app.screenshotUrls?.length || 0,
112
- ipadScreenshotCount: app.ipadScreenshotUrls?.length || 0,
113
- };
114
- break;
115
-
116
- case 'release':
117
- result.release = {
118
- currentVersion: app.version,
119
- originalReleaseDate: app.releaseDate,
120
- originalReleaseDateFormatted: formatDate(app.releaseDate),
121
- currentVersionReleaseDate: app.currentVersionReleaseDate,
122
- currentVersionReleaseDateFormatted: formatDate(
123
- app.currentVersionReleaseDate
124
- ),
125
- };
126
- break;
127
-
128
- case 'screenshots':
129
- result.screenshots = {
130
- iphone: {
131
- count: app.screenshotUrls?.length || 0,
132
- urls: app.screenshotUrls || [],
133
- },
134
- ipad: {
135
- count: app.ipadScreenshotUrls?.length || 0,
136
- urls: app.ipadScreenshotUrls || [],
137
- },
138
- };
139
- break;
140
-
141
- default:
142
- result.app = {
143
- id: app.id,
144
- name: app.name,
145
- artistName: app.artistName,
146
- url: app.url,
147
- iconUrl: app.iconUrl,
148
- formattedPrice: app.formattedPrice || 'Free',
149
- rating: app.averageUserRating?.toFixed(1) || 'N/A',
150
- ratingCount: app.userRatingCount || 0,
151
- primaryGenre: app.primaryGenreName,
152
- };
153
- }
154
-
155
- return {
156
- content: [
157
- {
158
- type: 'text' as const,
159
- text: JSON.stringify(result, null, 2),
160
- },
161
- ],
162
- };
163
- } catch (error) {
164
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
165
- return {
166
- content: [
167
- {
168
- type: 'text' as const,
169
- text: JSON.stringify(
170
- {
171
- success: false,
172
- error: errorMessage,
173
- },
174
- null,
175
- 2
176
- ),
177
- },
178
- ],
179
- };
180
- }
181
- }
182
- );
183
- }
@@ -1,108 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { z } from 'zod';
3
- import { AppStoreService } from '../../services/app-store-service';
4
- import { APP_STORE_CATEGORIES, Platform, PricingType } from '../../types';
5
-
6
- export function registerGetTrendingApps(server: McpServer) {
7
- server.registerTool(
8
- 'get_trending_apps',
9
- {
10
- description: 'Get top/trending apps from the App Store by category, platform, and pricing type',
11
- inputSchema: {
12
- platform: z
13
- .string()
14
- .optional()
15
- .describe('Platform to get apps for: iphone, ipad, mac, or tv. Defaults to iphone'),
16
- pricingType: z
17
- .string()
18
- .optional()
19
- .describe(
20
- 'Pricing type: free (top free apps), paid (top paid apps), or grossing (top grossing apps). Defaults to free'
21
- ),
22
- country: z
23
- .string()
24
- .optional()
25
- .describe('Country code for the App Store (e.g., US, GB, JP). Defaults to US'),
26
- categoryId: z
27
- .string()
28
- .optional()
29
- .describe(
30
- 'Optional category ID to filter by (e.g., 6014 for Games, 6007 for Productivity)'
31
- ),
32
- limit: z
33
- .number()
34
- .optional()
35
- .describe('Maximum number of results to return (1-100). Defaults to 25'),
36
- },
37
- },
38
- async ({ platform, pricingType, country, categoryId, limit }) => {
39
- const platformValue = (platform || 'iphone') as Platform;
40
- const pricingValue = (pricingType || 'free') as PricingType;
41
- const countryCode = country || 'US';
42
- const resultLimit = limit || 25;
43
-
44
- try {
45
- const apps = await AppStoreService.getTrendingApps(
46
- platformValue,
47
- pricingValue,
48
- countryCode,
49
- categoryId,
50
- resultLimit
51
- );
52
-
53
- const categoryInfo = categoryId
54
- ? APP_STORE_CATEGORIES.find((c) => c.id === categoryId)
55
- : null;
56
-
57
- const result = {
58
- success: true,
59
- count: apps.length,
60
- filters: {
61
- platform: platformValue,
62
- pricingType: pricingValue,
63
- country: countryCode,
64
- category: categoryInfo
65
- ? { id: categoryInfo.id, name: categoryInfo.name }
66
- : 'All Categories',
67
- },
68
- apps: apps.map((app, index) => ({
69
- rank: index + 1,
70
- id: app.id,
71
- name: app.name,
72
- artistName: app.artistName,
73
- url: app.url,
74
- iconUrl: app.iconUrl,
75
- price: app.formattedPrice || 'Free',
76
- primaryGenre: app.primaryGenreName,
77
- })),
78
- };
79
-
80
- return {
81
- content: [
82
- {
83
- type: 'text' as const,
84
- text: JSON.stringify(result, null, 2),
85
- },
86
- ],
87
- };
88
- } catch (error) {
89
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
90
- return {
91
- content: [
92
- {
93
- type: 'text' as const,
94
- text: JSON.stringify(
95
- {
96
- success: false,
97
- error: errorMessage,
98
- },
99
- null,
100
- 2
101
- ),
102
- },
103
- ],
104
- };
105
- }
106
- }
107
- );
108
- }
@@ -1,10 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { registerSearchApps } from './search-apps';
3
- import { registerGetAppInfo } from './get-app-info';
4
- import { registerGetTrendingApps } from './get-trending-apps';
5
-
6
- export function registerAllTools(server: McpServer) {
7
- registerSearchApps(server);
8
- registerGetAppInfo(server);
9
- registerGetTrendingApps(server);
10
- }
@@ -1,74 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { z } from 'zod';
3
- import { AppStoreService } from '../../services/app-store-service';
4
-
5
- export function registerSearchApps(server: McpServer) {
6
- server.registerTool(
7
- 'search_apps',
8
- {
9
- description: 'Search for apps on the App Store by name or keyword',
10
- inputSchema: {
11
- query: z.string().describe('The search term to find apps'),
12
- country: z
13
- .string()
14
- .optional()
15
- .describe('Country code for the App Store (e.g., US, GB, JP). Defaults to US'),
16
- limit: z
17
- .number()
18
- .optional()
19
- .describe('Maximum number of results to return (1-200). Defaults to 25'),
20
- },
21
- },
22
- async ({ query, country, limit }) => {
23
- const countryCode = country || 'US';
24
- const resultLimit = limit || 25;
25
- try {
26
- const apps = await AppStoreService.searchApps(query, countryCode, resultLimit);
27
-
28
- const result = {
29
- success: true,
30
- count: apps.length,
31
- country: countryCode,
32
- query: query,
33
- apps: apps.map((app) => ({
34
- id: app.id,
35
- name: app.name,
36
- artistName: app.artistName,
37
- url: app.url,
38
- iconUrl: app.iconUrl,
39
- price: app.formattedPrice || 'Free',
40
- rating: app.averageUserRating?.toFixed(1) || 'N/A',
41
- ratingCount: app.userRatingCount || 0,
42
- primaryGenre: app.primaryGenreName,
43
- })),
44
- };
45
-
46
- return {
47
- content: [
48
- {
49
- type: 'text' as const,
50
- text: JSON.stringify(result, null, 2),
51
- },
52
- ],
53
- };
54
- } catch (error) {
55
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
56
- return {
57
- content: [
58
- {
59
- type: 'text' as const,
60
- text: JSON.stringify(
61
- {
62
- success: false,
63
- error: errorMessage,
64
- },
65
- null,
66
- 2
67
- ),
68
- },
69
- ],
70
- };
71
- }
72
- }
73
- );
74
- }
@@ -1,236 +0,0 @@
1
- import axios from 'axios';
2
- import { App, Platform, PricingType } from '../types';
3
-
4
- const ITUNES_SEARCH_URL = 'https://itunes.apple.com/search';
5
- const ITUNES_LOOKUP_URL = 'https://itunes.apple.com/lookup';
6
- const ITUNES_RSS_BASE = 'https://itunes.apple.com';
7
-
8
- interface SearchResponse {
9
- resultCount: number;
10
- results: ItunesSearchResult[];
11
- }
12
-
13
- interface ItunesSearchResult {
14
- trackId: number;
15
- trackName: string;
16
- trackViewUrl: string;
17
- artworkUrl100: string;
18
- artistName: string;
19
- averageUserRating?: number;
20
- userRatingCount?: number;
21
- releaseDate?: string;
22
- currentVersionReleaseDate?: string;
23
- description?: string;
24
- version?: string;
25
- price?: number;
26
- formattedPrice?: string;
27
- primaryGenreName?: string;
28
- genres?: string[];
29
- screenshotUrls?: string[];
30
- ipadScreenshotUrls?: string[];
31
- bundleId?: string;
32
- minimumOsVersion?: string;
33
- fileSizeBytes?: string;
34
- contentAdvisoryRating?: string;
35
- }
36
-
37
- interface RssEntry {
38
- 'im:name': { label: string };
39
- 'im:image': { label: string }[];
40
- id: {
41
- label: string;
42
- attributes: { 'im:id': string };
43
- };
44
- 'im:artist': { label: string };
45
- 'im:releaseDate'?: { label: string };
46
- summary?: { label: string };
47
- 'im:price'?: { label: string };
48
- category?: {
49
- attributes: { label: string };
50
- };
51
- }
52
-
53
- interface RssResponse {
54
- feed: {
55
- entry: RssEntry[];
56
- };
57
- }
58
-
59
- export class AppStoreService {
60
- private static getFeedName(platform: Platform, pricingType: PricingType): string {
61
- const feedMap: Record<Platform, Record<PricingType, string>> = {
62
- iphone: {
63
- free: 'topfreeapplications',
64
- paid: 'toppaidapplications',
65
- grossing: 'topgrossingapplications',
66
- },
67
- ipad: {
68
- free: 'topfreeipadapplications',
69
- paid: 'toppaidipadapplications',
70
- grossing: 'topgrossingipadapplications',
71
- },
72
- mac: {
73
- free: 'topfreemacapps',
74
- paid: 'toppaidmacapps',
75
- grossing: 'topgrossingmacapps',
76
- },
77
- tv: {
78
- free: 'topfreeappletvapps',
79
- paid: 'toppaidappletvapps',
80
- grossing: 'topgrossingappletvapps',
81
- },
82
- };
83
-
84
- return feedMap[platform][pricingType];
85
- }
86
-
87
- private static buildRssUrl(
88
- countryCode: string,
89
- feedName: string,
90
- limit: number,
91
- categoryId?: string
92
- ): string {
93
- const genrePart = categoryId ? `/genre=${categoryId}` : '';
94
- return `${ITUNES_RSS_BASE}/${countryCode}/rss/${feedName}/limit=${limit}${genrePart}/json`;
95
- }
96
-
97
- private static parseSearchResult(item: ItunesSearchResult): App {
98
- return {
99
- id: String(item.trackId),
100
- name: item.trackName,
101
- url: item.trackViewUrl,
102
- iconUrl: item.artworkUrl100,
103
- artistName: item.artistName,
104
- averageUserRating: item.averageUserRating,
105
- userRatingCount: item.userRatingCount,
106
- releaseDate: item.releaseDate,
107
- currentVersionReleaseDate: item.currentVersionReleaseDate,
108
- description: item.description,
109
- version: item.version,
110
- price: item.price,
111
- formattedPrice: item.formattedPrice,
112
- primaryGenreName: item.primaryGenreName,
113
- genres: item.genres,
114
- screenshotUrls: item.screenshotUrls,
115
- ipadScreenshotUrls: item.ipadScreenshotUrls,
116
- bundleId: item.bundleId,
117
- minimumOsVersion: item.minimumOsVersion,
118
- fileSizeBytes: item.fileSizeBytes,
119
- contentAdvisoryRating: item.contentAdvisoryRating,
120
- };
121
- }
122
-
123
- private static parseRssEntry(entry: RssEntry): App {
124
- const images = entry['im:image'] || [];
125
- const largestImage = images.length > 0 ? images[images.length - 1].label : '';
126
-
127
- return {
128
- id: entry.id.attributes['im:id'],
129
- name: entry['im:name'].label,
130
- url: entry.id.label,
131
- iconUrl: largestImage,
132
- artistName: entry['im:artist'].label,
133
- releaseDate: entry['im:releaseDate']?.label,
134
- description: entry.summary?.label,
135
- formattedPrice: entry['im:price']?.label,
136
- primaryGenreName: entry.category?.attributes.label,
137
- };
138
- }
139
-
140
- static async searchApps(query: string, country: string = 'US', limit: number = 25): Promise<App[]> {
141
- const params = {
142
- term: query,
143
- country: country,
144
- media: 'software',
145
- entity: 'software',
146
- limit: limit,
147
- };
148
-
149
- const response = await axios.get<SearchResponse>(ITUNES_SEARCH_URL, { params });
150
- return response.data.results.map(this.parseSearchResult);
151
- }
152
-
153
- static async getAppDetails(appId: string, country: string = 'US'): Promise<App | null> {
154
- const params = {
155
- id: appId,
156
- country: country,
157
- };
158
-
159
- const response = await axios.get<SearchResponse>(ITUNES_LOOKUP_URL, { params });
160
-
161
- if (response.data.resultCount === 0) {
162
- return null;
163
- }
164
-
165
- return this.parseSearchResult(response.data.results[0]);
166
- }
167
-
168
- static async lookupMultipleApps(
169
- appIds: string[],
170
- country: string = 'US'
171
- ): Promise<Map<string, App>> {
172
- const params = {
173
- id: appIds.join(','),
174
- country: country,
175
- };
176
-
177
- const response = await axios.get<SearchResponse>(ITUNES_LOOKUP_URL, { params });
178
- const appMap = new Map<string, App>();
179
-
180
- for (const result of response.data.results) {
181
- const app = this.parseSearchResult(result);
182
- appMap.set(app.id, app);
183
- }
184
-
185
- return appMap;
186
- }
187
-
188
- static async getTrendingApps(
189
- platform: Platform = 'iphone',
190
- pricingType: PricingType = 'free',
191
- country: string = 'US',
192
- categoryId?: string,
193
- limit: number = 25
194
- ): Promise<App[]> {
195
- const feedName = this.getFeedName(platform, pricingType);
196
- const url = this.buildRssUrl(country, feedName, limit, categoryId);
197
-
198
- const response = await axios.get<RssResponse>(url);
199
- const entries = response.data.feed.entry || [];
200
-
201
- return entries.map(this.parseRssEntry);
202
- }
203
-
204
- static async getAppReleaseInfo(
205
- appId: string,
206
- country: string = 'US'
207
- ): Promise<{ releaseDate?: string; currentVersionReleaseDate?: string; version?: string } | null> {
208
- const app = await this.getAppDetails(appId, country);
209
-
210
- if (!app) {
211
- return null;
212
- }
213
-
214
- return {
215
- releaseDate: app.releaseDate,
216
- currentVersionReleaseDate: app.currentVersionReleaseDate,
217
- version: app.version,
218
- };
219
- }
220
-
221
- static async getAppScreenshots(
222
- appId: string,
223
- country: string = 'US'
224
- ): Promise<{ screenshotUrls: string[]; ipadScreenshotUrls: string[] } | null> {
225
- const app = await this.getAppDetails(appId, country);
226
-
227
- if (!app) {
228
- return null;
229
- }
230
-
231
- return {
232
- screenshotUrls: app.screenshotUrls || [],
233
- ipadScreenshotUrls: app.ipadScreenshotUrls || [],
234
- };
235
- }
236
- }
package/src/types.ts DELETED
@@ -1,77 +0,0 @@
1
- export interface App {
2
- id: string;
3
- name: string;
4
- url: string;
5
- iconUrl: string;
6
- artistName: string;
7
- averageUserRating?: number;
8
- userRatingCount?: number;
9
- releaseDate?: string;
10
- currentVersionReleaseDate?: string;
11
- description?: string;
12
- version?: string;
13
- price?: number;
14
- formattedPrice?: string;
15
- primaryGenreName?: string;
16
- genres?: string[];
17
- screenshotUrls?: string[];
18
- ipadScreenshotUrls?: string[];
19
- bundleId?: string;
20
- minimumOsVersion?: string;
21
- fileSizeBytes?: string;
22
- contentAdvisoryRating?: string;
23
- }
24
-
25
- export interface Category {
26
- id: string;
27
- name: string;
28
- emoji: string;
29
- }
30
-
31
- export type Platform = 'iphone' | 'ipad' | 'mac' | 'tv';
32
- export type PricingType = 'free' | 'paid' | 'grossing';
33
-
34
- export interface Country {
35
- code: string;
36
- name: string;
37
- }
38
-
39
- export const APP_STORE_CATEGORIES: Category[] = [
40
- { id: '6000', name: 'Business', emoji: '💼' },
41
- { id: '6001', name: 'Weather', emoji: '🌤️' },
42
- { id: '6002', name: 'Utilities', emoji: '🔧' },
43
- { id: '6003', name: 'Travel', emoji: '✈️' },
44
- { id: '6004', name: 'Sports', emoji: '⚽' },
45
- { id: '6005', name: 'Social Networking', emoji: '👥' },
46
- { id: '6006', name: 'Reference', emoji: '📚' },
47
- { id: '6007', name: 'Productivity', emoji: '📊' },
48
- { id: '6008', name: 'Photo & Video', emoji: '📷' },
49
- { id: '6009', name: 'News', emoji: '📰' },
50
- { id: '6010', name: 'Navigation', emoji: '🧭' },
51
- { id: '6011', name: 'Music', emoji: '🎵' },
52
- { id: '6012', name: 'Lifestyle', emoji: '🌟' },
53
- { id: '6013', name: 'Health & Fitness', emoji: '💪' },
54
- { id: '6014', name: 'Games', emoji: '🎮' },
55
- { id: '6015', name: 'Finance', emoji: '💰' },
56
- { id: '6016', name: 'Entertainment', emoji: '🎬' },
57
- { id: '6017', name: 'Education', emoji: '🎓' },
58
- { id: '6018', name: 'Books', emoji: '📖' },
59
- { id: '6020', name: 'Medical', emoji: '🏥' },
60
- { id: '6021', name: 'Magazines & Newspapers', emoji: '📰' },
61
- { id: '6022', name: 'Catalogs', emoji: '📋' },
62
- { id: '6023', name: 'Food & Drink', emoji: '🍔' },
63
- { id: '6024', name: 'Shopping', emoji: '🛒' },
64
- ];
65
-
66
- export const PLATFORMS: { value: Platform; label: string; emoji: string }[] = [
67
- { value: 'iphone', label: 'iPhone', emoji: '📱' },
68
- { value: 'ipad', label: 'iPad', emoji: '📲' },
69
- { value: 'mac', label: 'Mac', emoji: '💻' },
70
- { value: 'tv', label: 'Apple TV', emoji: '📺' },
71
- ];
72
-
73
- export const PRICING_TYPES: { value: PricingType; label: string; emoji: string }[] = [
74
- { value: 'free', label: 'Free', emoji: '🆓' },
75
- { value: 'paid', label: 'Paid', emoji: '💵' },
76
- { value: 'grossing', label: 'Top Grossing', emoji: '💰' },
77
- ];
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "commonjs",
5
- "lib": ["ES2022"],
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "resolveJsonModule": true,
13
- "declaration": true,
14
- "declarationMap": true,
15
- "inlineSourceMap": true,
16
- "noUnusedLocals": false,
17
- "noUnusedParameters": false
18
- },
19
- "include": ["src/**/*"],
20
- "exclude": ["node_modules", "dist"]
21
- }