pabal-store-api-mcp 1.1.0
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/README.md +95 -0
- package/bin/pabal-mcp.js +6 -0
- package/dist/src/core/clients/app-store-factory.d.ts +29 -0
- package/dist/src/core/clients/app-store-factory.js +72 -0
- package/dist/src/core/clients/client-factory-helpers.d.ts +7 -0
- package/dist/src/core/clients/client-factory-helpers.js +10 -0
- package/dist/src/core/clients/google-play-factory.d.ts +29 -0
- package/dist/src/core/clients/google-play-factory.js +72 -0
- package/dist/src/core/clients/types.d.ts +8 -0
- package/dist/src/core/clients/types.js +1 -0
- package/dist/src/core/helpers/formatters.d.ts +3 -0
- package/dist/src/core/helpers/formatters.js +38 -0
- package/dist/src/core/helpers/registration.d.ts +21 -0
- package/dist/src/core/helpers/registration.js +21 -0
- package/dist/src/core/helpers/translate-release-notes.d.ts +46 -0
- package/dist/src/core/helpers/translate-release-notes.js +87 -0
- package/dist/src/core/services/app-resolution-service.d.ts +14 -0
- package/dist/src/core/services/app-resolution-service.js +35 -0
- package/dist/src/core/services/app-store-service.d.ts +41 -0
- package/dist/src/core/services/app-store-service.js +266 -0
- package/dist/src/core/services/google-play-service.d.ts +36 -0
- package/dist/src/core/services/google-play-service.js +203 -0
- package/dist/src/core/services/service-helpers.d.ts +15 -0
- package/dist/src/core/services/service-helpers.js +31 -0
- package/dist/src/core/services/types.d.ts +81 -0
- package/dist/src/core/services/types.js +1 -0
- package/dist/src/core/workflows/version-info.d.ts +29 -0
- package/dist/src/core/workflows/version-info.js +100 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +279 -0
- package/dist/src/packages/common/errors/app-error.d.ts +39 -0
- package/dist/src/packages/common/errors/app-error.js +134 -0
- package/dist/src/packages/common/errors/error-codes.d.ts +63 -0
- package/dist/src/packages/common/errors/error-codes.js +71 -0
- package/dist/src/packages/common/errors/status-codes.d.ts +10 -0
- package/dist/src/packages/common/errors/status-codes.js +9 -0
- package/dist/src/packages/configs/aso-config/constants.d.ts +14 -0
- package/dist/src/packages/configs/aso-config/constants.js +102 -0
- package/dist/src/packages/configs/aso-config/locale-guards.d.ts +3 -0
- package/dist/src/packages/configs/aso-config/locale-guards.js +7 -0
- package/dist/src/packages/configs/aso-config/store.d.ts +11 -0
- package/dist/src/packages/configs/aso-config/store.js +11 -0
- package/dist/src/packages/configs/aso-config/types.d.ts +98 -0
- package/dist/src/packages/configs/aso-config/types.js +2 -0
- package/dist/src/packages/configs/aso-config/utils.d.ts +43 -0
- package/dist/src/packages/configs/aso-config/utils.js +223 -0
- package/dist/src/packages/configs/secrets-config/config.d.ts +12 -0
- package/dist/src/packages/configs/secrets-config/config.js +187 -0
- package/dist/src/packages/configs/secrets-config/constants.d.ts +1 -0
- package/dist/src/packages/configs/secrets-config/constants.js +1 -0
- package/dist/src/packages/configs/secrets-config/errors.d.ts +9 -0
- package/dist/src/packages/configs/secrets-config/errors.js +15 -0
- package/dist/src/packages/configs/secrets-config/registered-apps.d.ts +52 -0
- package/dist/src/packages/configs/secrets-config/registered-apps.js +108 -0
- package/dist/src/packages/configs/secrets-config/schemas.d.ts +21 -0
- package/dist/src/packages/configs/secrets-config/schemas.js +9 -0
- package/dist/src/packages/configs/secrets-config/types.d.ts +8 -0
- package/dist/src/packages/configs/secrets-config/types.js +1 -0
- package/dist/src/packages/stores/app-store/api-converters.d.ts +26 -0
- package/dist/src/packages/stores/app-store/api-converters.js +131 -0
- package/dist/src/packages/stores/app-store/api-endpoints.d.ts +33 -0
- package/dist/src/packages/stores/app-store/api-endpoints.js +157 -0
- package/dist/src/packages/stores/app-store/auth.d.ts +12 -0
- package/dist/src/packages/stores/app-store/auth.js +36 -0
- package/dist/src/packages/stores/app-store/client.d.ts +78 -0
- package/dist/src/packages/stores/app-store/client.js +637 -0
- package/dist/src/packages/stores/app-store/constants.d.ts +11 -0
- package/dist/src/packages/stores/app-store/constants.js +38 -0
- package/dist/src/packages/stores/app-store/generated-types.d.ts +118537 -0
- package/dist/src/packages/stores/app-store/generated-types.js +5 -0
- package/dist/src/packages/stores/app-store/types.d.ts +39 -0
- package/dist/src/packages/stores/app-store/types.js +9 -0
- package/dist/src/packages/stores/app-store/verify-auth.d.ts +16 -0
- package/dist/src/packages/stores/app-store/verify-auth.js +34 -0
- package/dist/src/packages/stores/play-store/api-converters.d.ts +58 -0
- package/dist/src/packages/stores/play-store/api-converters.js +209 -0
- package/dist/src/packages/stores/play-store/api-endpoints.d.ts +68 -0
- package/dist/src/packages/stores/play-store/api-endpoints.js +145 -0
- package/dist/src/packages/stores/play-store/client.d.ts +55 -0
- package/dist/src/packages/stores/play-store/client.js +628 -0
- package/dist/src/packages/stores/play-store/constants.d.ts +10 -0
- package/dist/src/packages/stores/play-store/constants.js +17 -0
- package/dist/src/packages/stores/play-store/types.d.ts +146 -0
- package/dist/src/packages/stores/play-store/types.js +9 -0
- package/dist/src/packages/stores/play-store/verify-auth.d.ts +13 -0
- package/dist/src/packages/stores/play-store/verify-auth.js +31 -0
- package/dist/src/tools/apps/add.d.ts +28 -0
- package/dist/src/tools/apps/add.js +307 -0
- package/dist/src/tools/apps/init.d.ts +58 -0
- package/dist/src/tools/apps/init.js +390 -0
- package/dist/src/tools/apps/search.d.ts +33 -0
- package/dist/src/tools/apps/search.js +147 -0
- package/dist/src/tools/aso/pull.d.ts +22 -0
- package/dist/src/tools/aso/pull.js +264 -0
- package/dist/src/tools/aso/push.d.ts +23 -0
- package/dist/src/tools/aso/push.js +189 -0
- package/dist/src/tools/auth/app-store.d.ts +9 -0
- package/dist/src/tools/auth/app-store.js +34 -0
- package/dist/src/tools/auth/check.d.ts +14 -0
- package/dist/src/tools/auth/check.js +50 -0
- package/dist/src/tools/auth/play-store.d.ts +9 -0
- package/dist/src/tools/auth/play-store.js +30 -0
- package/dist/src/tools/release/check-versions.d.ts +14 -0
- package/dist/src/tools/release/check-versions.js +65 -0
- package/dist/src/tools/release/create.d.ts +23 -0
- package/dist/src/tools/release/create.js +128 -0
- package/dist/src/tools/release/pull-notes.d.ts +22 -0
- package/dist/src/tools/release/pull-notes.js +151 -0
- package/dist/src/tools/release/update-notes.d.ts +110 -0
- package/dist/src/tools/release/update-notes.js +537 -0
- package/package.json +71 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { loadConfig } from "../../packages/configs/secrets-config/config.js";
|
|
2
|
+
import { getStoreTargets } from "../../packages/configs/aso-config/store.js";
|
|
3
|
+
import { AppStoreService } from "../../core/services/app-store-service.js";
|
|
4
|
+
import { GooglePlayService } from "../../core/services/google-play-service.js";
|
|
5
|
+
const appStoreService = new AppStoreService();
|
|
6
|
+
const googlePlayService = new GooglePlayService();
|
|
7
|
+
export async function getLatestVersions(input) {
|
|
8
|
+
const { store, bundleId, packageName, hasAppStore, hasGooglePlay, includePrompt = true, } = input;
|
|
9
|
+
const { includeAppStore, includeGooglePlay } = getStoreTargets(store);
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const messages = [];
|
|
12
|
+
messages.push(`\n📋 Checking latest versions from stores...\n`);
|
|
13
|
+
const result = { messages };
|
|
14
|
+
if (includeAppStore) {
|
|
15
|
+
if (!hasAppStore) {
|
|
16
|
+
const skipped = `⏭️ Skipping App Store (not registered for App Store)`;
|
|
17
|
+
messages.push(skipped);
|
|
18
|
+
result.appStore = { found: false, skipped };
|
|
19
|
+
}
|
|
20
|
+
else if (!config.appStore) {
|
|
21
|
+
const skipped = `⏭️ Skipping App Store (not configured in ~/.config/pabal-mcp/config.json)`;
|
|
22
|
+
messages.push(skipped);
|
|
23
|
+
result.appStore = { found: false, skipped };
|
|
24
|
+
}
|
|
25
|
+
else if (!bundleId) {
|
|
26
|
+
const skipped = `⏭️ Skipping App Store (no bundleId provided)`;
|
|
27
|
+
messages.push(skipped);
|
|
28
|
+
result.appStore = { found: false, skipped };
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const latest = await appStoreService.getLatestVersion(bundleId);
|
|
32
|
+
if (!latest.found) {
|
|
33
|
+
const errMsg = latest.error instanceof Error
|
|
34
|
+
? latest.error.message
|
|
35
|
+
: (latest.error ?? "");
|
|
36
|
+
const msg = errMsg
|
|
37
|
+
? `🍎 App Store: Check failed - ${errMsg}`
|
|
38
|
+
: `🍎 App Store: No version found (can create first version)`;
|
|
39
|
+
messages.push(msg);
|
|
40
|
+
result.appStore = { found: false, error: errMsg || undefined };
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const state = latest.state?.toUpperCase() || "UNKNOWN";
|
|
44
|
+
messages.push(`🍎 App Store: ${latest.versionString} (${state})`);
|
|
45
|
+
result.appStore = {
|
|
46
|
+
found: true,
|
|
47
|
+
versionString: latest.versionString,
|
|
48
|
+
state,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (includeGooglePlay) {
|
|
54
|
+
if (!hasGooglePlay) {
|
|
55
|
+
const skipped = `⏭️ Skipping Google Play (not registered for Google Play)`;
|
|
56
|
+
messages.push(skipped);
|
|
57
|
+
result.googlePlay = { found: false, skipped };
|
|
58
|
+
}
|
|
59
|
+
else if (!config.playStore) {
|
|
60
|
+
const skipped = `⏭️ Skipping Google Play (not configured in ~/.config/pabal-mcp/config.json)`;
|
|
61
|
+
messages.push(skipped);
|
|
62
|
+
result.googlePlay = { found: false, skipped };
|
|
63
|
+
}
|
|
64
|
+
else if (!packageName) {
|
|
65
|
+
const skipped = `⏭️ Skipping Google Play (no packageName provided)`;
|
|
66
|
+
messages.push(skipped);
|
|
67
|
+
result.googlePlay = { found: false, skipped };
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const latest = await googlePlayService.getLatestProductionRelease(packageName);
|
|
71
|
+
if (!latest.found) {
|
|
72
|
+
const errMsg = latest.error instanceof Error
|
|
73
|
+
? latest.error.message
|
|
74
|
+
: (latest.error ?? "");
|
|
75
|
+
const msg = errMsg
|
|
76
|
+
? `🤖 Google Play: Check failed - ${errMsg}`
|
|
77
|
+
: `🤖 Google Play: No version found (can create first version)`;
|
|
78
|
+
messages.push(msg);
|
|
79
|
+
result.googlePlay = { found: false, error: errMsg || undefined };
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const versionName = latest.versionName || latest.releaseName || "N/A";
|
|
83
|
+
const status = latest.status?.toUpperCase() || "UNKNOWN";
|
|
84
|
+
messages.push(`🤖 Google Play: ${versionName} (versionCodes: ${latest.versionCodes.join(", ")}, ${status})`);
|
|
85
|
+
result.googlePlay = {
|
|
86
|
+
found: true,
|
|
87
|
+
versionName,
|
|
88
|
+
versionCodes: latest.versionCodes,
|
|
89
|
+
status,
|
|
90
|
+
releaseName: latest.releaseName,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (includePrompt) {
|
|
96
|
+
messages.push(`\n💡 To create a new version, please provide the 'version' parameter.`);
|
|
97
|
+
messages.push(` Example: Call release-create tool with version="1.2.0".`);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
interface ToolInfo {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema?: z.ZodObject<any> | z.ZodTypeAny;
|
|
6
|
+
category?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function getToolInfos(): ToolInfo[];
|
|
9
|
+
export declare function startServer(): Promise<void>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { handleAuthCheck } from "./tools/auth/check.js";
|
|
6
|
+
import { handleSetupApps } from "./tools/apps/init.js";
|
|
7
|
+
import { handleAddApp } from "./tools/apps/add.js";
|
|
8
|
+
import { handleSearchApps } from "./tools/apps/search.js";
|
|
9
|
+
import { handleAsoPull } from "./tools/aso/pull.js";
|
|
10
|
+
import { handleAsoPush } from "./tools/aso/push.js";
|
|
11
|
+
import { handleAsoCreateVersion } from "./tools/release/create.js";
|
|
12
|
+
import { handleAsoPullReleaseNotes } from "./tools/release/pull-notes.js";
|
|
13
|
+
import { handleUpdateNotes } from "./tools/release/update-notes.js";
|
|
14
|
+
import { handleCheckLatestVersions } from "./tools/release/check-versions.js";
|
|
15
|
+
// MCP config sets cwd to project root, so we don't need to chdir
|
|
16
|
+
// Just verify we're in the right place
|
|
17
|
+
console.error(`[MCP] 📂 Working directory: ${process.cwd()}`);
|
|
18
|
+
const server = new McpServer({ name: "pabal-mcp", version: "0.0.1" }, {
|
|
19
|
+
instructions: "Provides tools for App Store/Play Store ASO.",
|
|
20
|
+
});
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Common Schemas
|
|
23
|
+
// ============================================================================
|
|
24
|
+
const storeSchema = z.enum(["appStore", "googlePlay", "both"]).optional();
|
|
25
|
+
const toolInfos = [];
|
|
26
|
+
/**
|
|
27
|
+
* Format parameters for logging (hide sensitive data)
|
|
28
|
+
*/
|
|
29
|
+
function formatParamsForLogging(params) {
|
|
30
|
+
if (!params || typeof params !== "object")
|
|
31
|
+
return "";
|
|
32
|
+
const safeParams = {};
|
|
33
|
+
const sensitiveKeys = [
|
|
34
|
+
"privateKey",
|
|
35
|
+
"serviceAccountJson",
|
|
36
|
+
"serviceAccountKey",
|
|
37
|
+
];
|
|
38
|
+
for (const [key, value] of Object.entries(params)) {
|
|
39
|
+
if (sensitiveKeys.some((sk) => key.toLowerCase().includes(sk.toLowerCase()))) {
|
|
40
|
+
safeParams[key] = "[REDACTED]";
|
|
41
|
+
}
|
|
42
|
+
else if (typeof value === "object" && value !== null) {
|
|
43
|
+
safeParams[key] = formatParamsForLogging(value);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
safeParams[key] = value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const entries = Object.entries(safeParams)
|
|
50
|
+
.filter(([_, v]) => v !== undefined && v !== null && v !== "")
|
|
51
|
+
.map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`)
|
|
52
|
+
.slice(0, 3); // Limit to first 3 params to keep it concise
|
|
53
|
+
return entries.length > 0 ? ` (${entries.join(", ")})` : "";
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Wrap handler with logging
|
|
57
|
+
*/
|
|
58
|
+
function wrapHandlerWithLogging(name, handler) {
|
|
59
|
+
return async (params) => {
|
|
60
|
+
const paramsStr = formatParamsForLogging(params);
|
|
61
|
+
console.error(`[MCP] 🔧 ${name}${paramsStr}`);
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
try {
|
|
64
|
+
const result = await handler(params);
|
|
65
|
+
const duration = Date.now() - startTime;
|
|
66
|
+
console.error(`[MCP] ✅ ${name} completed (${duration}ms)`);
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const duration = Date.now() - startTime;
|
|
71
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
72
|
+
console.error(`[MCP] ❌ ${name} failed (${duration}ms): ${errorMsg}`);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function registerToolWithInfo(name, info, handler, category) {
|
|
78
|
+
// Store tool info for documentation (keep zod schema)
|
|
79
|
+
toolInfos.push({
|
|
80
|
+
name,
|
|
81
|
+
description: info.description,
|
|
82
|
+
inputSchema: info.inputSchema,
|
|
83
|
+
category,
|
|
84
|
+
});
|
|
85
|
+
// Convert zod schema to plain object for MCP SDK
|
|
86
|
+
const mcpInfo = {
|
|
87
|
+
description: info.description,
|
|
88
|
+
};
|
|
89
|
+
if (info.inputSchema && info.inputSchema instanceof z.ZodObject) {
|
|
90
|
+
// Convert ZodObject to plain object format expected by MCP SDK
|
|
91
|
+
const shape = info.inputSchema.shape;
|
|
92
|
+
const plainSchema = {};
|
|
93
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
94
|
+
plainSchema[key] = value;
|
|
95
|
+
}
|
|
96
|
+
mcpInfo.inputSchema = plainSchema;
|
|
97
|
+
}
|
|
98
|
+
// Wrap handler with logging
|
|
99
|
+
const wrappedHandler = wrapHandlerWithLogging(name, handler);
|
|
100
|
+
server.registerTool(name, mcpInfo, wrappedHandler);
|
|
101
|
+
}
|
|
102
|
+
// Export tool info for documentation
|
|
103
|
+
export function getToolInfos() {
|
|
104
|
+
return toolInfos;
|
|
105
|
+
}
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Authentication (auth-*)
|
|
108
|
+
// ============================================================================
|
|
109
|
+
registerToolWithInfo("auth-check", {
|
|
110
|
+
description: "Check authentication status for App Store Connect / Google Play Console.",
|
|
111
|
+
inputSchema: z.object({
|
|
112
|
+
store: storeSchema.describe("Store to check (default: both)"),
|
|
113
|
+
}),
|
|
114
|
+
}, handleAuthCheck, "Authentication");
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// App Management (apps-*)
|
|
117
|
+
// ============================================================================
|
|
118
|
+
registerToolWithInfo("apps-init", {
|
|
119
|
+
description: "Query app list from store API and register automatically. App Store: auto-register all apps, Google Play: packageName required.",
|
|
120
|
+
inputSchema: z.object({
|
|
121
|
+
store: z
|
|
122
|
+
.enum(["appStore", "googlePlay"])
|
|
123
|
+
.optional()
|
|
124
|
+
.describe("Target store (default: appStore)"),
|
|
125
|
+
packageName: z
|
|
126
|
+
.string()
|
|
127
|
+
.optional()
|
|
128
|
+
.describe("Google Play package name (required when setting up Google Play)"),
|
|
129
|
+
}),
|
|
130
|
+
}, handleSetupApps, "App Management");
|
|
131
|
+
registerToolWithInfo("apps-add", {
|
|
132
|
+
description: "Register individual app by bundleId or packageName. Automatically checks both stores.",
|
|
133
|
+
inputSchema: z.object({
|
|
134
|
+
identifier: z
|
|
135
|
+
.string()
|
|
136
|
+
.describe("App identifier (bundleId or packageName, e.g., com.example.app)"),
|
|
137
|
+
slug: z
|
|
138
|
+
.string()
|
|
139
|
+
.optional()
|
|
140
|
+
.describe("Custom slug (if not specified, uses last part of identifier)"),
|
|
141
|
+
store: storeSchema.describe("Store to check (default: both)"),
|
|
142
|
+
}),
|
|
143
|
+
}, handleAddApp, "App Management");
|
|
144
|
+
registerToolWithInfo("apps-search", {
|
|
145
|
+
description: "Search registered apps. Returns all apps if called without query.",
|
|
146
|
+
inputSchema: z.object({
|
|
147
|
+
query: z
|
|
148
|
+
.string()
|
|
149
|
+
.optional()
|
|
150
|
+
.describe("Search term (slug, bundleId, packageName, name). Returns all apps if empty"),
|
|
151
|
+
store: z
|
|
152
|
+
.enum(["all", "appStore", "googlePlay"])
|
|
153
|
+
.optional()
|
|
154
|
+
.describe("Store filter (default: all)"),
|
|
155
|
+
}),
|
|
156
|
+
}, handleSearchApps, "App Management");
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// ASO Data Sync (aso-*)
|
|
159
|
+
// ============================================================================
|
|
160
|
+
registerToolWithInfo("aso-pull", {
|
|
161
|
+
description: "Fetch ASO data from App Store/Google Play and save to local cache.",
|
|
162
|
+
inputSchema: z.object({
|
|
163
|
+
app: z
|
|
164
|
+
.string()
|
|
165
|
+
.optional()
|
|
166
|
+
.describe("Registered app slug (app registered via apps-init)"),
|
|
167
|
+
packageName: z.string().optional().describe("Google Play package name"),
|
|
168
|
+
bundleId: z.string().optional().describe("App Store bundle ID"),
|
|
169
|
+
store: storeSchema.describe("Target store (default: both)"),
|
|
170
|
+
dryRun: z
|
|
171
|
+
.boolean()
|
|
172
|
+
.optional()
|
|
173
|
+
.describe("If true, only outputs result without actually saving"),
|
|
174
|
+
}),
|
|
175
|
+
}, handleAsoPull, "ASO Data Sync");
|
|
176
|
+
registerToolWithInfo("aso-push", {
|
|
177
|
+
description: "Push ASO data from local cache to App Store/Google Play.",
|
|
178
|
+
inputSchema: z.object({
|
|
179
|
+
app: z.string().optional().describe("Registered app slug"),
|
|
180
|
+
packageName: z.string().optional().describe("Google Play package name"),
|
|
181
|
+
bundleId: z.string().optional().describe("App Store bundle ID"),
|
|
182
|
+
store: storeSchema.describe("Target store (default: both)"),
|
|
183
|
+
uploadImages: z
|
|
184
|
+
.boolean()
|
|
185
|
+
.optional()
|
|
186
|
+
.describe("Whether to upload images as well"),
|
|
187
|
+
dryRun: z
|
|
188
|
+
.boolean()
|
|
189
|
+
.optional()
|
|
190
|
+
.describe("If true, only outputs result without actually pushing"),
|
|
191
|
+
}),
|
|
192
|
+
}, handleAsoPush, "ASO Data Sync");
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Release Management (release-*)
|
|
195
|
+
// ============================================================================
|
|
196
|
+
registerToolWithInfo("release-check-versions", {
|
|
197
|
+
description: "Check latest versions from App Store/Google Play.",
|
|
198
|
+
inputSchema: z.object({
|
|
199
|
+
app: z.string().optional().describe("Registered app slug"),
|
|
200
|
+
packageName: z.string().optional().describe("Google Play package name"),
|
|
201
|
+
bundleId: z.string().optional().describe("App Store bundle ID"),
|
|
202
|
+
store: storeSchema.describe("Target store (default: both)"),
|
|
203
|
+
}),
|
|
204
|
+
}, handleCheckLatestVersions, "Release Management");
|
|
205
|
+
registerToolWithInfo("release-create", {
|
|
206
|
+
description: "Create a new version on App Store/Google Play. If version is not provided, checks and displays latest versions from each store.",
|
|
207
|
+
inputSchema: z.object({
|
|
208
|
+
app: z.string().optional().describe("Registered app slug"),
|
|
209
|
+
packageName: z.string().optional().describe("Google Play package name"),
|
|
210
|
+
bundleId: z.string().optional().describe("App Store bundle ID"),
|
|
211
|
+
version: z
|
|
212
|
+
.string()
|
|
213
|
+
.optional()
|
|
214
|
+
.describe("Version string to create (e.g., 1.2.0). If not provided, will check and display latest versions."),
|
|
215
|
+
store: storeSchema.describe("Target store (default: both)"),
|
|
216
|
+
versionCodes: z
|
|
217
|
+
.array(z.number())
|
|
218
|
+
.optional()
|
|
219
|
+
.describe("Version code array for Google Play (required when creating Google Play version)"),
|
|
220
|
+
}),
|
|
221
|
+
}, handleAsoCreateVersion, "Release Management");
|
|
222
|
+
registerToolWithInfo("release-pull-notes", {
|
|
223
|
+
description: "Fetch release notes from App Store/Google Play.",
|
|
224
|
+
inputSchema: z.object({
|
|
225
|
+
app: z.string().optional().describe("Registered app slug"),
|
|
226
|
+
packageName: z.string().optional().describe("Google Play package name"),
|
|
227
|
+
bundleId: z.string().optional().describe("App Store bundle ID"),
|
|
228
|
+
store: storeSchema.describe("Target store (default: both)"),
|
|
229
|
+
dryRun: z
|
|
230
|
+
.boolean()
|
|
231
|
+
.optional()
|
|
232
|
+
.describe("If true, only outputs result without actually saving"),
|
|
233
|
+
}),
|
|
234
|
+
}, handleAsoPullReleaseNotes, "Release Management");
|
|
235
|
+
registerToolWithInfo("release-update-notes", {
|
|
236
|
+
description: "Update release notes (What's New) for App Store/Google Play version.",
|
|
237
|
+
inputSchema: z.object({
|
|
238
|
+
app: z.string().optional().describe("Registered app slug"),
|
|
239
|
+
packageName: z.string().optional().describe("Google Play package name"),
|
|
240
|
+
bundleId: z.string().optional().describe("App Store bundle ID"),
|
|
241
|
+
store: storeSchema.describe("Target store (default: both)"),
|
|
242
|
+
versionId: z
|
|
243
|
+
.string()
|
|
244
|
+
.optional()
|
|
245
|
+
.describe("App Store version ID (auto-detects editable version if not specified)"),
|
|
246
|
+
whatsNew: z
|
|
247
|
+
.record(z.string(), z.string())
|
|
248
|
+
.optional()
|
|
249
|
+
.describe('Release notes by locale (e.g., { "en-US": "Bug fixes", "ko": "Bug fixes" })'),
|
|
250
|
+
text: z
|
|
251
|
+
.string()
|
|
252
|
+
.optional()
|
|
253
|
+
.describe("Source text to translate to all supported languages"),
|
|
254
|
+
sourceLocale: z
|
|
255
|
+
.string()
|
|
256
|
+
.optional()
|
|
257
|
+
.describe("Source locale (default: en-US)"),
|
|
258
|
+
}),
|
|
259
|
+
}, handleUpdateNotes, "Release Management");
|
|
260
|
+
async function main() {
|
|
261
|
+
console.error("[MCP] 🚀 Starting pabal-mcp server...");
|
|
262
|
+
const transport = new StdioServerTransport();
|
|
263
|
+
await server.connect(transport);
|
|
264
|
+
console.error("[MCP] ✅ Server connected and ready");
|
|
265
|
+
}
|
|
266
|
+
// Exported for CLI entrypoint (bin/pabal-mcp.js) so npx can start the server
|
|
267
|
+
export async function startServer() {
|
|
268
|
+
return main();
|
|
269
|
+
}
|
|
270
|
+
// Only start server if this file is run directly (not imported)
|
|
271
|
+
// Check if the current file is the main module being executed
|
|
272
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
|
|
273
|
+
fileURLToPath(import.meta.url) === process.argv[1];
|
|
274
|
+
if (isMainModule) {
|
|
275
|
+
startServer().catch((error) => {
|
|
276
|
+
console.error("MCP server failed to start", error);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface AppErrorJSON {
|
|
2
|
+
status: number;
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
details?: unknown;
|
|
6
|
+
}
|
|
7
|
+
type AppErrorParams = {
|
|
8
|
+
status: number;
|
|
9
|
+
code: string;
|
|
10
|
+
message: string;
|
|
11
|
+
details?: unknown;
|
|
12
|
+
cause?: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Standardized application error with HTTP-style status codes.
|
|
16
|
+
* Use factory methods (e.g. AppError.notFound) for consistency.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AppError extends Error {
|
|
19
|
+
status: number;
|
|
20
|
+
code: string;
|
|
21
|
+
details?: unknown;
|
|
22
|
+
constructor({ status, code, message, details, cause }: AppErrorParams);
|
|
23
|
+
static badRequest(code: string, message: string, details?: unknown): AppError;
|
|
24
|
+
static validation(code: string, message: string, details?: unknown): AppError;
|
|
25
|
+
static unauthorized(code: string, message: string, details?: unknown): AppError;
|
|
26
|
+
static forbidden(code: string, message: string, details?: unknown): AppError;
|
|
27
|
+
static notFound(code: string, message: string, details?: unknown): AppError;
|
|
28
|
+
static conflict(code: string, message: string, details?: unknown): AppError;
|
|
29
|
+
static configMissing(code: string, message: string, details?: unknown): AppError;
|
|
30
|
+
static io(code: string, message: string, details?: unknown): AppError;
|
|
31
|
+
static fileNotFound(code: string, message: string, details?: unknown): AppError;
|
|
32
|
+
static internal(code: string, message: string, details?: unknown, cause?: unknown): AppError;
|
|
33
|
+
/**
|
|
34
|
+
* Wrap an unknown error into an AppError while preserving the message.
|
|
35
|
+
*/
|
|
36
|
+
static wrap(error: unknown, fallbackStatus?: number, fallbackCode?: string, overrideMessage?: string): AppError;
|
|
37
|
+
toJSON(): AppErrorJSON;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { ERROR_CODES } from "./error-codes.js";
|
|
2
|
+
import { HTTP_STATUS } from "./status-codes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Standardized application error with HTTP-style status codes.
|
|
5
|
+
* Use factory methods (e.g. AppError.notFound) for consistency.
|
|
6
|
+
*/
|
|
7
|
+
export class AppError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
code;
|
|
10
|
+
details;
|
|
11
|
+
constructor({ status, code, message, details, cause }) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "AppError";
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.details = details;
|
|
17
|
+
if (cause !== undefined) {
|
|
18
|
+
// Preserve original error for debugging without leaking in toJSON
|
|
19
|
+
this.cause = cause;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Factory helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
static badRequest(code, message, details) {
|
|
26
|
+
return new AppError({
|
|
27
|
+
status: HTTP_STATUS.BAD_REQUEST,
|
|
28
|
+
code,
|
|
29
|
+
message,
|
|
30
|
+
details,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
static validation(code, message, details) {
|
|
34
|
+
return new AppError({
|
|
35
|
+
status: HTTP_STATUS.UNPROCESSABLE_ENTITY,
|
|
36
|
+
code,
|
|
37
|
+
message,
|
|
38
|
+
details,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
static unauthorized(code, message, details) {
|
|
42
|
+
return new AppError({
|
|
43
|
+
status: HTTP_STATUS.UNAUTHORIZED,
|
|
44
|
+
code,
|
|
45
|
+
message,
|
|
46
|
+
details,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
static forbidden(code, message, details) {
|
|
50
|
+
return new AppError({
|
|
51
|
+
status: HTTP_STATUS.FORBIDDEN,
|
|
52
|
+
code,
|
|
53
|
+
message,
|
|
54
|
+
details,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
static notFound(code, message, details) {
|
|
58
|
+
return new AppError({
|
|
59
|
+
status: HTTP_STATUS.NOT_FOUND,
|
|
60
|
+
code,
|
|
61
|
+
message,
|
|
62
|
+
details,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
static conflict(code, message, details) {
|
|
66
|
+
return new AppError({
|
|
67
|
+
status: HTTP_STATUS.CONFLICT,
|
|
68
|
+
code,
|
|
69
|
+
message,
|
|
70
|
+
details,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
static configMissing(code, message, details) {
|
|
74
|
+
return new AppError({
|
|
75
|
+
status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
|
|
76
|
+
code,
|
|
77
|
+
message,
|
|
78
|
+
details,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
static io(code, message, details) {
|
|
82
|
+
return new AppError({
|
|
83
|
+
status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
|
|
84
|
+
code,
|
|
85
|
+
message,
|
|
86
|
+
details,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
static fileNotFound(code, message, details) {
|
|
90
|
+
return new AppError({
|
|
91
|
+
status: HTTP_STATUS.NOT_FOUND,
|
|
92
|
+
code,
|
|
93
|
+
message,
|
|
94
|
+
details,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
static internal(code, message, details, cause) {
|
|
98
|
+
return new AppError({
|
|
99
|
+
status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
|
|
100
|
+
code,
|
|
101
|
+
message,
|
|
102
|
+
details,
|
|
103
|
+
cause,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Wrap an unknown error into an AppError while preserving the message.
|
|
108
|
+
*/
|
|
109
|
+
static wrap(error, fallbackStatus = HTTP_STATUS.INTERNAL_SERVER_ERROR, fallbackCode = ERROR_CODES.INTERNAL_ERROR, overrideMessage) {
|
|
110
|
+
if (error instanceof AppError)
|
|
111
|
+
return error;
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
return new AppError({
|
|
114
|
+
status: fallbackStatus,
|
|
115
|
+
code: fallbackCode,
|
|
116
|
+
message: overrideMessage ?? error.message,
|
|
117
|
+
cause: error,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return new AppError({
|
|
121
|
+
status: fallbackStatus,
|
|
122
|
+
code: fallbackCode,
|
|
123
|
+
message: overrideMessage ?? String(error),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
toJSON() {
|
|
127
|
+
return {
|
|
128
|
+
status: this.status,
|
|
129
|
+
code: this.code,
|
|
130
|
+
message: this.message,
|
|
131
|
+
...(this.details ? { details: this.details } : {}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare const ERROR_CODES: {
|
|
2
|
+
readonly INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
3
|
+
readonly CLIENT_FACTORY_ERROR: "CLIENT_FACTORY_ERROR";
|
|
4
|
+
readonly CONFIG_ERROR: "CONFIG_ERROR";
|
|
5
|
+
readonly CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND";
|
|
6
|
+
readonly CONFIG_AUTH_NOT_FOUND: "CONFIG_AUTH_NOT_FOUND";
|
|
7
|
+
readonly CONFIG_READ_FAILED: "CONFIG_READ_FAILED";
|
|
8
|
+
readonly REGISTERED_APPS_READ_FAILED: "REGISTERED_APPS_READ_FAILED";
|
|
9
|
+
readonly REGISTERED_APPS_WRITE_FAILED: "REGISTERED_APPS_WRITE_FAILED";
|
|
10
|
+
readonly REGISTERED_APP_DUPLICATE: "REGISTERED_APP_DUPLICATE";
|
|
11
|
+
readonly REGISTERED_APP_NOT_FOUND: "REGISTERED_APP_NOT_FOUND";
|
|
12
|
+
readonly REGISTERED_APP_STORE_INFO_MISSING: "REGISTERED_APP_STORE_INFO_MISSING";
|
|
13
|
+
readonly CONFIG_NOT_FOUND_SKIP: "CONFIG_NOT_FOUND";
|
|
14
|
+
readonly IDENTIFIER_MISSING: "IDENTIFIER_MISSING";
|
|
15
|
+
readonly NO_DATA_FOUND: "NO_DATA_FOUND";
|
|
16
|
+
readonly APP_IDENTIFIER_MISSING: "APP_IDENTIFIER_MISSING";
|
|
17
|
+
readonly APP_NOT_FOUND: "APP_NOT_FOUND";
|
|
18
|
+
readonly APP_RESOLUTION_FAILED: "APP_RESOLUTION_FAILED";
|
|
19
|
+
readonly APP_STORE_CLIENT_CONFIG_MISSING: "APP_STORE_CLIENT_CONFIG_MISSING";
|
|
20
|
+
readonly APP_STORE_BUNDLE_ID_INVALID: "APP_STORE_BUNDLE_ID_INVALID";
|
|
21
|
+
readonly APP_STORE_CONFIG_LOAD_FAILED: "APP_STORE_CONFIG_LOAD_FAILED";
|
|
22
|
+
readonly APP_STORE_CONFIG_MISSING: "APP_STORE_CONFIG_MISSING";
|
|
23
|
+
readonly APP_STORE_ISSUER_ID_INVALID: "APP_STORE_ISSUER_ID_INVALID";
|
|
24
|
+
readonly APP_STORE_KEY_ID_INVALID: "APP_STORE_KEY_ID_INVALID";
|
|
25
|
+
readonly APP_STORE_PRIVATE_KEY_INVALID: "APP_STORE_PRIVATE_KEY_INVALID";
|
|
26
|
+
readonly APP_STORE_PRIVATE_KEY_FORMAT_INVALID: "APP_STORE_PRIVATE_KEY_FORMAT_INVALID";
|
|
27
|
+
readonly APP_STORE_CLIENT_CREATE_FAILED: "APP_STORE_CLIENT_CREATE_FAILED";
|
|
28
|
+
readonly GOOGLE_PLAY_CLIENT_CONFIG_MISSING: "GOOGLE_PLAY_CLIENT_CONFIG_MISSING";
|
|
29
|
+
readonly GOOGLE_PLAY_PACKAGE_NAME_INVALID: "GOOGLE_PLAY_PACKAGE_NAME_INVALID";
|
|
30
|
+
readonly GOOGLE_PLAY_CONFIG_LOAD_FAILED: "GOOGLE_PLAY_CONFIG_LOAD_FAILED";
|
|
31
|
+
readonly GOOGLE_PLAY_CONFIG_MISSING: "GOOGLE_PLAY_CONFIG_MISSING";
|
|
32
|
+
readonly GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_INVALID: "GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_INVALID";
|
|
33
|
+
readonly GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_FORMAT_INVALID: "GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_FORMAT_INVALID";
|
|
34
|
+
readonly GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PARSE_FAILED: "GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PARSE_FAILED";
|
|
35
|
+
readonly GOOGLE_PLAY_CLIENT_CREATE_FAILED: "GOOGLE_PLAY_CLIENT_CREATE_FAILED";
|
|
36
|
+
readonly APP_STORE_LIST_APPS_FAILED: "APP_STORE_LIST_APPS_FAILED";
|
|
37
|
+
readonly APP_STORE_FETCH_APP_INFO_FAILED: "APP_STORE_FETCH_APP_INFO_FAILED";
|
|
38
|
+
readonly APP_STORE_GET_LATEST_VERSION_FAILED: "APP_STORE_GET_LATEST_VERSION_FAILED";
|
|
39
|
+
readonly APP_STORE_VERSION_NOT_EDITABLE: "APP_STORE_VERSION_NOT_EDITABLE";
|
|
40
|
+
readonly APP_STORE_UPDATE_RELEASE_NOTES_PARTIAL: "APP_STORE_UPDATE_RELEASE_NOTES_PARTIAL";
|
|
41
|
+
readonly APP_STORE_UPDATE_RELEASE_NOTES_FAILED: "APP_STORE_UPDATE_RELEASE_NOTES_FAILED";
|
|
42
|
+
readonly APP_STORE_PULL_RELEASE_NOTES_FAILED: "APP_STORE_PULL_RELEASE_NOTES_FAILED";
|
|
43
|
+
readonly APP_STORE_CREATE_VERSION_FAILED: "APP_STORE_CREATE_VERSION_FAILED";
|
|
44
|
+
readonly APP_STORE_PUSH_FAILED: "APP_STORE_PUSH_FAILED";
|
|
45
|
+
readonly APP_STORE_STATE_ERROR: "APP_STORE_STATE_ERROR";
|
|
46
|
+
readonly APP_STORE_CREATE_VERSION_FOR_STATE_ERROR_FAILED: "APP_STORE_CREATE_VERSION_FOR_STATE_ERROR_FAILED";
|
|
47
|
+
readonly APP_STORE_VERIFY_AUTH_FAILED: "APP_STORE_VERIFY_AUTH_FAILED";
|
|
48
|
+
readonly GOOGLE_PLAY_FETCH_APP_INFO_FAILED: "GOOGLE_PLAY_FETCH_APP_INFO_FAILED";
|
|
49
|
+
readonly GOOGLE_PLAY_GET_LATEST_RELEASE_FAILED: "GOOGLE_PLAY_GET_LATEST_RELEASE_FAILED";
|
|
50
|
+
readonly GOOGLE_PLAY_RELEASE_NOTES_EMPTY: "GOOGLE_PLAY_RELEASE_NOTES_EMPTY";
|
|
51
|
+
readonly GOOGLE_PLAY_UPDATE_RELEASE_NOTES_PARTIAL: "GOOGLE_PLAY_UPDATE_RELEASE_NOTES_PARTIAL";
|
|
52
|
+
readonly GOOGLE_PLAY_UPDATE_RELEASE_NOTES_FAILED: "GOOGLE_PLAY_UPDATE_RELEASE_NOTES_FAILED";
|
|
53
|
+
readonly GOOGLE_PLAY_PULL_RELEASE_NOTES_FAILED: "GOOGLE_PLAY_PULL_RELEASE_NOTES_FAILED";
|
|
54
|
+
readonly GOOGLE_PLAY_CREATE_VERSION_FAILED: "GOOGLE_PLAY_CREATE_VERSION_FAILED";
|
|
55
|
+
readonly GOOGLE_PLAY_PUSH_FAILED: "GOOGLE_PLAY_PUSH_FAILED";
|
|
56
|
+
readonly GOOGLE_PLAY_VERIFY_AUTH_FAILED: "GOOGLE_PLAY_VERIFY_AUTH_FAILED";
|
|
57
|
+
readonly ASO_GOOGLE_PLAY_DATA_PARSE_FAILED: "ASO_GOOGLE_PLAY_DATA_PARSE_FAILED";
|
|
58
|
+
readonly ASO_APP_STORE_DATA_PARSE_FAILED: "ASO_APP_STORE_DATA_PARSE_FAILED";
|
|
59
|
+
readonly ASO_DATA_SAVE_FAILED: "ASO_DATA_SAVE_FAILED";
|
|
60
|
+
readonly ASO_IMAGE_DOWNLOAD_FAILED: "ASO_IMAGE_DOWNLOAD_FAILED";
|
|
61
|
+
readonly ASO_LOCAL_ASSET_NOT_FOUND: "ASO_LOCAL_ASSET_NOT_FOUND";
|
|
62
|
+
};
|
|
63
|
+
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|