@applaunchflow/mcp 0.1.0 → 0.1.2

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/build/index.js CHANGED
@@ -17,7 +17,7 @@ import { registerVariantTools } from "./tools/variants.js";
17
17
  const SERVER_INSTRUCTIONS = `
18
18
  AppLaunchFlow MCP is screenshot-focused at this stage.
19
19
  Only use it for screenshot project setup, screenshot uploads, screenshot template generation, variants, and direct screenshot layout editing.
20
- Do not treat this MCP as an ASO, localization, graphics, or promo-video assistant.
20
+ Do not treat this MCP as an ASO, localization, or graphics assistant.
21
21
 
22
22
  Use AppLaunchFlow MCP as an execution tool, not a questionnaire.
23
23
 
@@ -109,13 +109,16 @@ export function registerLayoutTools(server, client) {
109
109
  server.registerTool("transform_layout", {
110
110
  title: "Transform Layout",
111
111
  description: "Apply transform operations to an existing layout. Primary editing tool for text, screenshots, colors, and structure changes. " +
112
+ "IMPORTANT: Always call get_layout FIRST to inspect the current layout state before using this tool. Never transform blindly. " +
112
113
  "RULES: " +
113
- "1. nodeType is REQUIRED in every operation target. The backend rejects operations without it. " +
114
- "2. Omit nodeId to update ALL nodes of that type in the target screens. This is the efficient way to do bulk changes (e.g. change all illustration colors: {nodeType:'illustration', screens:'all'} with changes {primaryColor:'#xxx'}). " +
114
+ "1. nodeType is REQUIRED in every operation target. " +
115
+ "2. Omit nodeId to update ALL nodes of that type in the target screens. " +
115
116
  "3. Use screens:'all' to target every screen at once. Do NOT send one operation per screen when the same change applies to all. " +
116
- "4. For add_node, changes MUST include an 'id' field. " +
117
- "5. To add new screens, first add empty screen containers, then populate them in a SECOND call using selector 'screenId:<id>'. " +
118
- "6. Default to layouts:['mobile']. Only include tablet/desktop if the user asks.",
117
+ "4. Dot-notation is supported for deep updates without replacing the whole object. Example: {'richContent.attrs.defaultFontSize': 96} updates only the font size inside richContent.attrs, preserving all other fields. Always use dot-notation for nested property changes. " +
118
+ "5. For add_node, changes MUST include an 'id' field. " +
119
+ "6. To add new screens, first add empty screen containers, then populate them in a SECOND call using selector 'screenId:<id>'. " +
120
+ "7. Default to layouts:['mobile']. Only include tablet/desktop if the user asks. " +
121
+ "8. FONT SIZE: The rendered font size is controlled ONLY by 'richContent.attrs.defaultFontSize' (pixel value). To change font size, use dot-notation: {'richContent.attrs.defaultFontSize': 80}. Do NOT use 'fontSizeScale' — that property is for promo videos only and has NO effect on screenshot rendering.",
119
122
  inputSchema: {
120
123
  generationId: z.string().uuid(),
121
124
  language: z.string(),
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { z } from "zod";
2
3
  import { fail, ok } from "./utils.js";
3
4
  const contentTypeEnum = z.enum(["screenshots"]);
@@ -26,9 +27,27 @@ export function registerVariantTools(server, client) {
26
27
  label: z.string().optional(),
27
28
  setActive: z.boolean().optional(),
28
29
  },
29
- }, async (args) => {
30
+ }, async (args, extra) => {
30
31
  try {
31
- return ok(await client.createVariant(args), "Created variant");
32
+ const result = await client.createVariant(args);
33
+ const variantId = result?.variant?.id;
34
+ const generationId = args.generationId;
35
+ if (variantId && generationId) {
36
+ const editorUrl = `${client.credentials.baseUrl}/editor?projectId=${generationId}&variantId=${variantId}&device=phone`;
37
+ try {
38
+ await server.server.elicitInput({
39
+ mode: "url",
40
+ elicitationId: randomUUID(),
41
+ message: "Opening the new variant in the editor.",
42
+ url: editorUrl,
43
+ }, { signal: extra.signal });
44
+ }
45
+ catch {
46
+ // Client may not support URL elicitation — include URL in response
47
+ }
48
+ return ok({ ...result, editorUrl }, "Created variant");
49
+ }
50
+ return ok(result, "Created variant");
32
51
  }
33
52
  catch (error) {
34
53
  return fail(error);
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@applaunchflow/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for AppLaunchFlow — create App Store & Google Play screenshots with AI.",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/ynnickw/applaunchflow-mcp"
9
+ },
6
10
  "type": "module",
7
11
  "bin": {
8
12
  "applaunchflow-mcp": "./build/index.js"
@@ -1,23 +0,0 @@
1
- import { z } from "zod";
2
- import { fail, ok } from "./utils.js";
3
- import { lookupStoreMetadata } from "./store-metadata.js";
4
- export function registerAnalysisTools(server, client) {
5
- server.registerTool("analyze_app", {
6
- title: "Analyze App",
7
- description: "Analyze an App Store or Google Play URL and return app metadata",
8
- inputSchema: {
9
- app: z.string().min(1),
10
- country: z.string().length(2).optional(),
11
- },
12
- }, async ({ app, country }) => {
13
- try {
14
- const data = await lookupStoreMetadata(client, app, country || "us");
15
- return ok(data, data.provider === "google_play"
16
- ? "Fetched Google Play metadata"
17
- : "Fetched App Store metadata");
18
- }
19
- catch (error) {
20
- return fail(error);
21
- }
22
- });
23
- }
@@ -1,84 +0,0 @@
1
- import { z } from "zod";
2
- import { fail, ok } from "./utils.js";
3
- export function registerAsoTools(server, client) {
4
- server.registerTool("generate_aso_copy", {
5
- title: "Generate ASO Copy",
6
- description: "Generate ASO copy for a project",
7
- inputSchema: {
8
- generationId: z.string().uuid(),
9
- variantId: z.string().uuid().optional(),
10
- metadata: z.record(z.any()).optional(),
11
- competitorAppIds: z.array(z.number()).optional(),
12
- },
13
- }, async (args) => {
14
- try {
15
- return ok(await client.generateAsoCopy(args), "Generated ASO copy");
16
- }
17
- catch (error) {
18
- return fail(error);
19
- }
20
- });
21
- server.registerTool("get_aso_copy", {
22
- title: "Get ASO Copy",
23
- description: "Get ASO copy for a project",
24
- inputSchema: {
25
- generationId: z.string().uuid(),
26
- variantId: z.string().uuid().optional(),
27
- },
28
- }, async ({ generationId, variantId }) => {
29
- try {
30
- return ok(await client.getAsoCopy(generationId, variantId), "Fetched ASO copy");
31
- }
32
- catch (error) {
33
- return fail(error);
34
- }
35
- });
36
- server.registerTool("update_aso_copy", {
37
- title: "Update ASO Copy",
38
- description: "Persist edited ASO copy",
39
- inputSchema: {
40
- generationId: z.string().uuid(),
41
- variantId: z.string().uuid().optional(),
42
- asoCopy: z.record(z.any()),
43
- },
44
- }, async (args) => {
45
- try {
46
- return ok(await client.updateAsoCopy(args), "Updated ASO copy");
47
- }
48
- catch (error) {
49
- return fail(error);
50
- }
51
- });
52
- server.registerTool("translate_aso_copy", {
53
- title: "Translate ASO Copy",
54
- description: "Translate ASO copy into target languages",
55
- inputSchema: {
56
- generationId: z.string().uuid(),
57
- variantId: z.string().uuid().optional(),
58
- targetLanguages: z.array(z.string()).min(1),
59
- },
60
- }, async (args) => {
61
- try {
62
- return ok(await client.translateAsoCopy(args), "Translated ASO copy");
63
- }
64
- catch (error) {
65
- return fail(error);
66
- }
67
- });
68
- server.registerTool("suggest_competitors", {
69
- title: "Suggest Competitors",
70
- description: "Suggest competitor apps for ASO analysis",
71
- inputSchema: {
72
- appName: z.string(),
73
- category: z.string(),
74
- platform: z.enum(["ios", "android", "both"]).optional(),
75
- },
76
- }, async (args) => {
77
- try {
78
- return ok(await client.suggestCompetitors(args), "Fetched competitors");
79
- }
80
- catch (error) {
81
- return fail(error);
82
- }
83
- });
84
- }
@@ -1,69 +0,0 @@
1
- import { z } from "zod";
2
- import { fail, ok } from "./utils.js";
3
- export function registerGraphicsTools(server, client) {
4
- server.registerTool("generate_graphics", {
5
- title: "Generate Graphics",
6
- description: "Generate social graphics suggestions for a project",
7
- inputSchema: {
8
- generationId: z.string().uuid(),
9
- templateId: z.string(),
10
- },
11
- }, async (args) => {
12
- try {
13
- return ok(await client.generateGraphics(args), "Generated graphics");
14
- }
15
- catch (error) {
16
- return fail(error);
17
- }
18
- });
19
- server.registerTool("get_graphics", {
20
- title: "Get Graphics",
21
- description: "Get saved social graphics for a project",
22
- inputSchema: {
23
- projectId: z.string().uuid(),
24
- variantId: z.string().uuid().optional(),
25
- },
26
- }, async ({ projectId, variantId }) => {
27
- try {
28
- return ok(await client.getGraphics(projectId, variantId), "Fetched graphics");
29
- }
30
- catch (error) {
31
- return fail(error);
32
- }
33
- });
34
- server.registerTool("save_graphics", {
35
- title: "Save Graphics",
36
- description: "Persist social graphics layouts for a project",
37
- inputSchema: {
38
- generationId: z.string().uuid(),
39
- variantId: z.string().uuid().optional(),
40
- socialTemplateId: z.string(),
41
- socialPrimaryFormat: z.enum([
42
- "og",
43
- "x_post",
44
- "play_store_feature",
45
- "x_header",
46
- "linkedin_banner",
47
- ]),
48
- graphics: z
49
- .array(z.object({
50
- format: z.enum([
51
- "og",
52
- "x_post",
53
- "play_store_feature",
54
- "x_header",
55
- "linkedin_banner",
56
- ]),
57
- layout: z.record(z.any()),
58
- }))
59
- .min(1),
60
- },
61
- }, async (args) => {
62
- try {
63
- return ok(await client.saveGraphics(args), "Saved graphics");
64
- }
65
- catch (error) {
66
- return fail(error);
67
- }
68
- });
69
- }
@@ -1,66 +0,0 @@
1
- export function extractAppStoreId(input) {
2
- if (/^\d+$/.test(input)) {
3
- return input;
4
- }
5
- const match = input.match(/id(\d+)/);
6
- return match ? match[1] : null;
7
- }
8
- export async function fetchGooglePlayMetadata(urlString) {
9
- const url = new URL(urlString);
10
- const packageName = url.searchParams.get("id");
11
- if (!packageName) {
12
- throw new Error("Google Play URL must include an id query parameter");
13
- }
14
- const response = await fetch(url.toString(), {
15
- headers: {
16
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
17
- "Accept-Language": "en-US,en;q=0.9",
18
- },
19
- });
20
- if (!response.ok) {
21
- throw new Error(`Google Play request failed with status ${response.status}`);
22
- }
23
- const html = await response.text();
24
- const scriptMatch = html.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/);
25
- if (!scriptMatch) {
26
- throw new Error("Unable to parse Google Play metadata");
27
- }
28
- const payload = JSON.parse(scriptMatch[1]);
29
- return {
30
- provider: "google_play",
31
- packageName,
32
- appName: payload.name || null,
33
- description: payload.description || null,
34
- category: payload.applicationCategory || null,
35
- iconUrl: payload.image || null,
36
- rating: payload.aggregateRating?.ratingValue || null,
37
- ratingCount: payload.aggregateRating?.ratingCount || null,
38
- url: url.toString(),
39
- };
40
- }
41
- export async function lookupStoreMetadata(client, app, country = "us") {
42
- if (app.includes("play.google.com")) {
43
- return fetchGooglePlayMetadata(app);
44
- }
45
- const appId = extractAppStoreId(app);
46
- if (!appId) {
47
- throw new Error("Provide an App Store URL, Google Play URL, or App Store numeric id");
48
- }
49
- const lookup = await client.lookupAppStore(appId, country);
50
- const appResult = lookup.results?.[0];
51
- if (!appResult) {
52
- throw new Error("App Store app not found");
53
- }
54
- return {
55
- provider: "apple",
56
- appleAppId: appId,
57
- appName: appResult.trackName || null,
58
- description: appResult.description || null,
59
- iconUrl: appResult.artworkUrl512 || appResult.artworkUrl100 || null,
60
- category: appResult.primaryGenreName || null,
61
- screenshotUrls: appResult.screenshotUrls || [],
62
- averageUserRating: appResult.averageUserRating || null,
63
- userRatingCount: appResult.userRatingCount || null,
64
- appStoreUrl: appResult.trackViewUrl || null,
65
- };
66
- }