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 +21 -0
- package/README.md +1 -5
- package/dist/index.js +1 -1
- package/package.json +23 -4
- package/.claude/CLAUDE.md +0 -29
- package/.claude/settings.local.json +0 -24
- package/.prettierrc +0 -7
- package/eslint.config.js +0 -9
- package/src/index.ts +0 -24
- package/src/mcp/resources/countries.ts +0 -234
- package/src/mcp/resources/index.ts +0 -117
- package/src/mcp/tools/get-app-info.ts +0 -183
- package/src/mcp/tools/get-trending-apps.ts +0 -108
- package/src/mcp/tools/index.ts +0 -10
- package/src/mcp/tools/search-apps.ts +0 -74
- package/src/services/app-store-service.ts +0 -236
- package/src/types.ts +0 -77
- package/tsconfig.json +0 -21
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
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
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
|
-
}
|
package/src/mcp/tools/index.ts
DELETED
|
@@ -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
|
-
}
|