@tanqory/dev-mcp 0.1.2 → 0.1.3
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 +3 -1
- package/package.json +2 -2
- package/src/server.mjs +71 -23
package/README.md
CHANGED
|
@@ -75,7 +75,9 @@ next.
|
|
|
75
75
|
| `screenshot {path, viewport, locale?, market?, fullPage?}` | Returns a PNG, as an MCP image block, from local Playwright. |
|
|
76
76
|
| `list_dev_stores` | Runs `tanqory stores --json` and reports the store this theme is linked to. |
|
|
77
77
|
| `get_store_data_summary` | Calls `GET /__tq/store-summary` on the dev server: the products, collections, markets, languages and content you can design against. |
|
|
78
|
-
| `push_draft
|
|
78
|
+
| `push_draft {confirm?, planDigest?}` | Two steps. Without `confirm`: runs `tanqory theme push --build-only --dry-run` and returns the plan (files, bytes, sha256, target development store, `planDigest`); nothing is uploaded. With `confirm: true` and that `planDigest`: runs `tanqory theme push --build-only --plan-digest <d>`: the platform builds the theme in its sandboxed lane, runs conformance and signs it, **private to your development store**. `plan_changed` if the theme changed in between. Nothing is installed or published. |
|
|
79
|
+
| `get_build_status {buildId?}` | Runs `tanqory theme builds [--id <buildId>] --json`: one build (status, verdict, conformance summary, signature, error) or the store's recent builds and the daily quota. |
|
|
80
|
+
| `get_preview_url` | **Refuses today** with `preview_unavailable`: a pushed build is not installed as a draft yet, so no preview URL exists. It never invents one. |
|
|
79
81
|
|
|
80
82
|
**Prompts:** `new-theme`, `new-section`, `fix-check-report` and `port-from-another-template-language`.
|
|
81
83
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanqory/dev-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Local MCP server (stdio) for building Tanqory storefront themes with coding agents: versioned docs and schemas, kit examples, section scaffolding, theme checks, and pages and screenshots of the running dev server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tanqory",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"zod": "^3.25.76"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"create-tanqory-theme": "0.2.
|
|
46
|
+
"create-tanqory-theme": "0.2.6"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"registry": "https://npm.pkg.github.com"
|
package/src/server.mjs
CHANGED
|
@@ -35,7 +35,7 @@ const INSTRUCTIONS = `Tanqory theme development (Theme ABI v2). Use these tools,
|
|
|
35
35
|
4. Verify with check_theme, not \`npm run check\`: fix every failure it reports, including ones you did not cause, until passed is true.
|
|
36
36
|
5. Stop the dev server with stop_dev_server (shell fallback \`npm run dev -- --stop\`). Never pkill, killall or a pid found by name.
|
|
37
37
|
Never start \`tanqory mcp\`, Playwright or \`theme check\` yourself from a sandboxed shell: Chromium cannot start there; these tools do it.
|
|
38
|
-
You are not done until check_theme returned passed: true and you have seen the change in a screenshot; never say a check is green otherwise. Every storefront function (cart, variants, search, filters, markets, forms, SEO, i18n, live edit) comes from @tanqory/theme-kit; the theme owns markup and CSS. This server never calls a model, never takes a store id and never publishes.`
|
|
38
|
+
You are not done until check_theme returned passed: true and you have seen the change in a screenshot; never say a check is green otherwise. Every storefront function (cart, variants, search, filters, markets, forms, SEO, i18n, live edit) comes from @tanqory/theme-kit; the theme owns markup and CSS. This server never calls a model, never takes a store id and never publishes; push_draft builds and signs privately for your development store and installs nothing.`
|
|
39
39
|
|
|
40
40
|
const errorSchema = z.object({ code: z.string(), message: z.string(), hint: z.string().optional() }).passthrough()
|
|
41
41
|
const base = { ok: z.boolean().describe('false when the tool could not do what was asked; see error'), error: errorSchema.optional() }
|
|
@@ -597,49 +597,97 @@ Two steps: call WITHOUT confirm to get the plan (every file, full new contents,
|
|
|
597
597
|
}),
|
|
598
598
|
)
|
|
599
599
|
|
|
600
|
-
// ──
|
|
601
|
-
const
|
|
602
|
-
code: '
|
|
600
|
+
// ── platform builds (P5): push_draft builds + signs privately; install/preview arrive with P6 ──
|
|
601
|
+
const PREVIEW_REFUSAL = {
|
|
602
|
+
code: 'preview_unavailable',
|
|
603
603
|
message:
|
|
604
|
-
|
|
605
|
-
hint: '
|
|
606
|
-
|
|
604
|
+
'Installing a pushed theme as an unpublished draft, and its preview URL, are not available yet: push_draft builds, checks and signs the theme privately for your development store, and installs nothing. No preview URL exists, so none is returned.',
|
|
605
|
+
hint: 'Use localhost (dev_server_status / screenshot) to look at the theme. push_draft + get_build_status tell you whether the platform build passed.',
|
|
606
|
+
}
|
|
607
|
+
const buildOut = { ...base, mode: z.string().optional(), planDigest: z.string().optional(), buildId: z.string().optional(), verdict: z.string().optional(), build: z.any().optional(), store: z.any().optional(), theme: z.any().optional(), upload: z.any().optional(), notUploaded: z.any().optional(), builds: z.array(z.any()).optional(), quota: z.any().optional() }
|
|
608
|
+
const buildLines = (b) => {
|
|
609
|
+
const c = b?.conformance ?? {}
|
|
610
|
+
return [
|
|
611
|
+
`build ${b?.id}: ${b?.status} (verdict ${b?.verdict})${b?.signed ? ', signed' : ''}, private to store ${b?.storeId}`,
|
|
612
|
+
...(c.status ? [`conformance: ${c.status}${c.basis ? ` on the ${c.basis}` : ''}`] : []),
|
|
613
|
+
...(c.warnings ?? []).map((w) => `budget warning: ${w.name} ${w.value} ${w.unit} (budget ${w.budget})`),
|
|
614
|
+
...(b?.error ? [`${b.status}: [${b.error.kind}${b.error.code ? `/${b.error.code}` : ''}] ${b.error.message}${b.error.path ? ` (${b.error.path})` : ''}`] : []),
|
|
615
|
+
]
|
|
607
616
|
}
|
|
608
|
-
const refusalOut = { ...base, refused: z.literal(true).optional() }
|
|
609
|
-
const refuse = () => ({ isError: true, content: [{ type: 'text', text: `${REFUSAL.code}: ${REFUSAL.message}\nWhat to do instead: ${REFUSAL.instead.join('; ')}. ${REFUSAL.hint}` }], structuredContent: { ok: false, refused: true, error: REFUSAL } })
|
|
610
617
|
|
|
611
618
|
server.registerTool(
|
|
612
619
|
'push_draft',
|
|
613
620
|
{
|
|
614
|
-
title: '
|
|
615
|
-
description:
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
621
|
+
title: 'Build the theme on the platform (private, signed; installs nothing)',
|
|
622
|
+
description:
|
|
623
|
+
'Uploads the theme SOURCE (git-tracked files, .gitignore respected; node_modules, dist, reports, .tanqory/dev and credential files are never uploaded) to the platform\'s sandboxed build lane for your linked DEVELOPMENT store, and waits for build → conformance → signing (`tanqory theme push --build-only`). Two steps: call WITHOUT confirm to get the plan (files, bytes, sha256, target store, planDigest; nothing is uploaded). Then call again with confirm: true and that planDigest; if the theme changed in between it refuses with plan_changed. The result is a signed build PRIVATE to that development store: nothing is installed, previewed or published, and there is no preview URL yet. Run check_theme first: a theme that fails it fails the platform build too. Needs a developer login (`tanqory login`) and a linked development store.',
|
|
624
|
+
inputSchema: {
|
|
625
|
+
confirm: z.boolean().optional().describe('true = upload the plan identified by planDigest'),
|
|
626
|
+
planDigest: z.string().max(100).optional().describe('From the plan step; required with confirm: true'),
|
|
627
|
+
},
|
|
628
|
+
outputSchema: buildOut,
|
|
629
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
619
630
|
},
|
|
620
|
-
guard(async () =>
|
|
631
|
+
guard(async ({ confirm, planDigest }) => {
|
|
632
|
+
const root = needTheme()
|
|
633
|
+
if (!confirm) {
|
|
634
|
+
const env = await runCli(['theme', 'push', '--build-only', '--dry-run'], { ...cliOpts, root })
|
|
635
|
+
if (!env.ok) return fail(env.error?.code ?? 'push_plan_failed', env.error?.message ?? '`tanqory theme push --dry-run` failed', env.error?.hint)
|
|
636
|
+
const d = env.data ?? {}
|
|
637
|
+
const text = [
|
|
638
|
+
`PLAN (nothing uploaded): ${d.upload?.files} files, ${d.upload?.bytes} bytes, sha256 ${String(d.upload?.sha256 ?? '').slice(0, 12)}…, theme "${d.theme?.handle}" → development store ${d.store?.handle ?? d.store?.id}.`,
|
|
639
|
+
...(d.notUploaded?.secrets?.length ? [`Never uploaded (look like credentials): ${d.notUploaded.secrets.join(', ')}`] : []),
|
|
640
|
+
`To build it, call push_draft again with confirm: true, planDigest: "${d.planDigest}". The build is private to that development store; nothing is installed.`,
|
|
641
|
+
].join('\n')
|
|
642
|
+
return done({ mode: 'plan', planDigest: d.planDigest, store: d.store, theme: d.theme, upload: d.upload, notUploaded: d.notUploaded }, text)
|
|
643
|
+
}
|
|
644
|
+
if (!planDigest) return fail('plan_digest_required', 'confirm: true needs the planDigest of a plan you reviewed.', 'Call push_draft without confirm first.')
|
|
645
|
+
const env = await runCli(['theme', 'push', '--build-only', '--plan-digest', planDigest], { ...cliOpts, root, timeoutMs: 25 * 60 * 1000 })
|
|
646
|
+
const d = env.data ?? {}
|
|
647
|
+
if (!env.ok) {
|
|
648
|
+
const lines = d.build ? buildLines(d.build) : []
|
|
649
|
+
return {
|
|
650
|
+
isError: true,
|
|
651
|
+
content: [{ type: 'text', text: [`${env.error?.code}: ${env.error?.message}`, ...lines, env.error?.hint ? `What to do: ${env.error.hint}` : ''].filter(Boolean).join('\n') }],
|
|
652
|
+
structuredContent: { ok: false, error: env.error ?? { code: 'push_failed', message: 'push failed' }, ...(d.build ? { mode: 'build', buildId: d.build.id, verdict: d.build.verdict, build: d.build, store: d.store, theme: d.theme } : {}), ...(d.planDigest ? { planDigest: d.planDigest } : {}) },
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return done(
|
|
656
|
+
{ mode: 'build', planDigest: d.planDigest, buildId: d.buildId, verdict: d.verdict, build: d.build, store: d.store, theme: d.theme, upload: d.upload, quota: d.quota },
|
|
657
|
+
[...buildLines(d.build), 'Nothing was installed or published; there is no preview URL yet. Follow up with get_build_status if it is still pending.'].join('\n'),
|
|
658
|
+
)
|
|
659
|
+
}),
|
|
621
660
|
)
|
|
622
661
|
server.registerTool(
|
|
623
662
|
'get_build_status',
|
|
624
663
|
{
|
|
625
|
-
title: 'Status of
|
|
626
|
-
description:
|
|
627
|
-
|
|
628
|
-
|
|
664
|
+
title: 'Status of your platform theme builds',
|
|
665
|
+
description:
|
|
666
|
+
'With buildId: that build of your linked development store (status, verdict, conformance summary, signature, error with kind/code/path). Without: the store\'s recent builds and the daily build quota (`tanqory theme builds --json`). Read-only.',
|
|
667
|
+
inputSchema: { buildId: z.string().uuid().optional().describe('From push_draft; omit for the recent builds') },
|
|
668
|
+
outputSchema: buildOut,
|
|
629
669
|
annotations: { ...READ, openWorldHint: true },
|
|
630
670
|
},
|
|
631
|
-
guard(async () =>
|
|
671
|
+
guard(async ({ buildId }) => {
|
|
672
|
+
const root = needTheme()
|
|
673
|
+
const env = await runCli(['theme', 'builds', ...(buildId ? ['--id', buildId] : [])], { ...cliOpts, root })
|
|
674
|
+
if (!env.ok) return fail(env.error?.code ?? 'builds_failed', env.error?.message ?? '`tanqory theme builds` failed', env.error?.hint)
|
|
675
|
+
const d = env.data ?? {}
|
|
676
|
+
if (buildId) return done({ buildId, verdict: d.build?.verdict, build: d.build, store: d.store }, buildLines(d.build).join('\n'))
|
|
677
|
+
const builds = d.builds ?? []
|
|
678
|
+
return done({ builds, store: d.store, quota: d.quota }, [`${builds.length} recent build(s) for ${d.store?.handle ?? d.store?.id}; ${d.quota?.used ?? '?'} of ${d.quota?.limit ?? '?'} used in 24 h`, ...builds.map((b) => `- ${b.id} ${b.status} (${b.verdict})${b.theme?.handle ? ` theme ${b.theme.handle}` : ''} ${b.createdAt}`)].join('\n'))
|
|
679
|
+
}),
|
|
632
680
|
)
|
|
633
681
|
server.registerTool(
|
|
634
682
|
'get_preview_url',
|
|
635
683
|
{
|
|
636
684
|
title: 'Preview URL of a draft (not available yet)',
|
|
637
|
-
description: `Would return the shareable preview and editor URLs of
|
|
685
|
+
description: `Would return the shareable preview and editor URLs of an installed draft. ALWAYS REFUSES TODAY with ${PREVIEW_REFUSAL.code}: push_draft builds and signs privately but installs nothing, so no preview URL exists. Never invent one; localhost (dev_server_status) is the only preview today.`,
|
|
638
686
|
inputSchema: {},
|
|
639
|
-
outputSchema:
|
|
687
|
+
outputSchema: { ...base, refused: z.literal(true).optional() },
|
|
640
688
|
annotations: { ...READ, openWorldHint: true },
|
|
641
689
|
},
|
|
642
|
-
guard(async () =>
|
|
690
|
+
guard(async () => ({ isError: true, content: [{ type: 'text', text: `${PREVIEW_REFUSAL.code}: ${PREVIEW_REFUSAL.message}\nWhat to do instead: ${PREVIEW_REFUSAL.hint}` }], structuredContent: { ok: false, refused: true, error: PREVIEW_REFUSAL } })),
|
|
643
691
|
)
|
|
644
692
|
|
|
645
693
|
// ── resources: every document as tanqory://docs/<id> ──────────────────────
|