@stackable-labs/mcp-app-extension 1.5.0 → 1.7.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/dist/index.js +231 -105
- package/dist/server.js +231 -105
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
-
import { IDENTITY_EVENT, ACTIVITY_EVENT, SURFACE_TARGET, TEMPLATE_FLAVORS, PERMISSIONS, CAPABILITY_PERMISSION_MAP, EVENT_HOOK_PERMISSION_MAP, ALLOWED_ICONS, UI_TAGS, UI_TAG_ATTRIBUTES, tagToComponentName } from '@stackable-labs/sdk-extension-contracts';
|
|
4
|
+
import { IDENTITY_EVENT, ACTIVITY_EVENT, SURFACE_TARGET, TEMPLATE_FLAVORS, PERMISSIONS, CAPABILITY_PERMISSION_MAP, EVENT_HOOK_PERMISSION_MAP, ALLOWED_ICONS, UI_TAGS, UI_TAG_CATEGORIES, UI_TAG_ATTRIBUTES, tagToComponentName } from '@stackable-labs/sdk-extension-contracts';
|
|
5
|
+
import { validatePatternFormat } from '@stackable-labs/lib-contracts';
|
|
5
6
|
import fs4, { readFile, mkdir, writeFile, constants } from 'fs/promises';
|
|
6
7
|
import path, { join } from 'path';
|
|
7
8
|
import os, { homedir } from 'os';
|
|
@@ -382,6 +383,22 @@ var frontmatter = (meta) => {
|
|
|
382
383
|
var generateCoreReference = () => CORE_CONTENT;
|
|
383
384
|
|
|
384
385
|
// ../../sdk/extension/ai-docs/src/generators/components.ts
|
|
386
|
+
var CATEGORY_ORDER = [
|
|
387
|
+
"layout",
|
|
388
|
+
"text",
|
|
389
|
+
"input",
|
|
390
|
+
"navigation",
|
|
391
|
+
"feedback",
|
|
392
|
+
"composite"
|
|
393
|
+
];
|
|
394
|
+
var CATEGORY_LABELS = {
|
|
395
|
+
layout: "Layout",
|
|
396
|
+
text: "Text",
|
|
397
|
+
input: "Input",
|
|
398
|
+
navigation: "Navigation",
|
|
399
|
+
feedback: "Feedback",
|
|
400
|
+
composite: "Composite"
|
|
401
|
+
};
|
|
385
402
|
var generateComponents = () => {
|
|
386
403
|
const fm = frontmatter({
|
|
387
404
|
root: false,
|
|
@@ -389,13 +406,31 @@ var generateComponents = () => {
|
|
|
389
406
|
description: "Available UI components with allowed attributes per tag",
|
|
390
407
|
globs: ["packages/extension/src/**/*.tsx"]
|
|
391
408
|
});
|
|
392
|
-
const
|
|
409
|
+
const tagsByCategory = /* @__PURE__ */ new Map();
|
|
393
410
|
for (const tag of UI_TAGS) {
|
|
394
|
-
const
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
411
|
+
const category = UI_TAG_CATEGORIES[tag];
|
|
412
|
+
const bucket = tagsByCategory.get(category) ?? [];
|
|
413
|
+
bucket.push(tag);
|
|
414
|
+
tagsByCategory.set(category, bucket);
|
|
415
|
+
}
|
|
416
|
+
for (const tags of tagsByCategory.values()) {
|
|
417
|
+
tags.sort();
|
|
418
|
+
}
|
|
419
|
+
const componentLines = [];
|
|
420
|
+
for (const category of CATEGORY_ORDER) {
|
|
421
|
+
const tags = tagsByCategory.get(category);
|
|
422
|
+
if (!tags || tags.length === 0) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
componentLines.push(`## ${CATEGORY_LABELS[category]}`);
|
|
398
426
|
componentLines.push("");
|
|
427
|
+
for (const tag of tags) {
|
|
428
|
+
const attrs = UI_TAG_ATTRIBUTES[tag];
|
|
429
|
+
const name = tagToComponentName(tag);
|
|
430
|
+
componentLines.push(`### \`<ui.${name}>\` (\`${tag}\`)`);
|
|
431
|
+
componentLines.push(`Allowed attributes: ${attrs.map((a2) => `\`${a2}\``).join(", ")}`);
|
|
432
|
+
componentLines.push("");
|
|
433
|
+
}
|
|
399
434
|
}
|
|
400
435
|
const iconList = ALLOWED_ICONS.map((icon) => `\`${icon}\``).join(", ");
|
|
401
436
|
return `${fm}
|
|
@@ -492,12 +527,12 @@ Access capabilities via the \`useCapabilities()\` hook:
|
|
|
492
527
|
const capabilities = useCapabilities()
|
|
493
528
|
\`\`\`
|
|
494
529
|
|
|
495
|
-
## data.query \u2014
|
|
496
|
-
The
|
|
530
|
+
## data.query \u2014 Platform-Mediated Requests
|
|
531
|
+
The Stackable platform handles the API call. Extension sends an action name + params, the platform returns data.
|
|
497
532
|
- **Permission required:** \`data:query\`
|
|
498
533
|
- **Usage:** \`capabilities.data.query<T>(payload: ApiRequest): Promise<T>\`
|
|
499
534
|
- **ApiRequest shape:** \`{ action: string; [key: string]: unknown }\`
|
|
500
|
-
- **When to use:** When the
|
|
535
|
+
- **When to use:** When the platform handles the API integration
|
|
501
536
|
|
|
502
537
|
\`\`\`tsx
|
|
503
538
|
const result = await capabilities.data.query<Customer>({
|
|
@@ -543,8 +578,8 @@ const result = await capabilities.data.fetch('https://api.example.com/orders', {
|
|
|
543
578
|
|
|
544
579
|
> See [Instance Settings](./instance-settings) for the full schema-declaration + storage-mode story, including which field types accept \`secret: true\`.
|
|
545
580
|
|
|
546
|
-
## context.read \u2014 Read
|
|
547
|
-
Read
|
|
581
|
+
## context.read \u2014 Read Platform Context
|
|
582
|
+
Read framework-provided context (customer ID, email, extension settings, etc.).
|
|
548
583
|
- **Permission required:** \`context:read\`
|
|
549
584
|
- **Usage:** \`capabilities.context.read(): Promise<ContextData>\`
|
|
550
585
|
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, settings?: Record<string, unknown>, [key: string]: unknown }\`
|
|
@@ -572,7 +607,7 @@ Non-secret settings declared in \`settingsSchema\` are automatically available v
|
|
|
572
607
|
- No new permission needed \u2014 \`context:read\` is the only gate
|
|
573
608
|
|
|
574
609
|
## actions.toast \u2014 Show Toast Notifications
|
|
575
|
-
Display a toast notification in the
|
|
610
|
+
Display a toast notification in the framework widget's UI.
|
|
576
611
|
- **Permission required:** \`actions:toast\`
|
|
577
612
|
- **Usage:** \`capabilities.actions.toast(payload: ToastPayload): Promise<void>\`
|
|
578
613
|
- **ToastPayload:** \`{ message: string, type?: 'success'|'error'|'info'|'warning', duration?: number }\`
|
|
@@ -581,8 +616,8 @@ Display a toast notification in the host UI.
|
|
|
581
616
|
capabilities.actions.toast({ message: 'Saved!', type: 'success' })
|
|
582
617
|
\`\`\`
|
|
583
618
|
|
|
584
|
-
## actions.invoke \u2014 Invoke
|
|
585
|
-
Trigger
|
|
619
|
+
## actions.invoke \u2014 Invoke Platform Actions
|
|
620
|
+
Trigger framework-defined actions (e.g., open a new conversation, set conversation tags/fields).
|
|
586
621
|
- **Permission required:** \`actions:invoke\`
|
|
587
622
|
- **Usage:** \`capabilities.actions.invoke<T>(action: string, payload?: Record<string, unknown>): Promise<T>\`
|
|
588
623
|
- **Available actions:**
|
|
@@ -611,7 +646,7 @@ await capabilities.actions.invoke('setConversationFields', [
|
|
|
611
646
|
**Zendesk constraints:** Tags max 20, auto-lowercased/sanitized. Fields require \`web_widget_conversation_ticket_metadata\` feature flag. Both \`conversationTags\` and \`conversationFields\` **replace** on each call (not additive).
|
|
612
647
|
|
|
613
648
|
## events:identity \u2014 Identity Event Subscription
|
|
614
|
-
Subscribe to real-time identity events (login, logout, refresh, expired) pushed from the host.
|
|
649
|
+
Subscribe to real-time identity events (login, logout, refresh, expired) pushed from the host via the framework.
|
|
615
650
|
- **Permission required:** \`events:identity\`
|
|
616
651
|
- **Manifest events array:** Declare specific events to listen for (e.g. \`["identity:login", "identity:logout"]\`)
|
|
617
652
|
- **Hook:** \`useIdentityEvent(eventType, handler)\`
|
|
@@ -651,7 +686,7 @@ ${HOOK_SNIPPETS["events:messaging"]}
|
|
|
651
686
|
\`\`\`
|
|
652
687
|
|
|
653
688
|
## events:activity \u2014 Activity Event Subscription
|
|
654
|
-
Subscribe to
|
|
689
|
+
Subscribe to activity events (e.g. page views, clicks, purchases) pushed from the host via the framework.
|
|
655
690
|
- **Permission required:** \`events:activity\`
|
|
656
691
|
- **Manifest events array:** Declare specific events to listen for (e.g. \`["activity:product_view", "activity:add_to_cart"]\`) \u2014 manifest uses fully-qualified strings
|
|
657
692
|
- **Hook:** \`useActivityEvent(eventType, handler)\` \u2014 \`ActivityEventHandler\` type exported for use with \`useCallback\`
|
|
@@ -684,7 +719,7 @@ ${HOOK_SNIPPETS["events:activity"]}
|
|
|
684
719
|
**Generic alternative:** \`useEvent('activity:product_view', handler)\` \u2014 a cross-domain hook that accepts fully-qualified event types. Domain wildcard (e.g., \`'activity'\`) receives all events in that domain.
|
|
685
720
|
|
|
686
721
|
## extend:identity \u2014 Identity Claim Enrichment
|
|
687
|
-
Enrich identity JWT claims before signing. The
|
|
722
|
+
Enrich identity JWT claims before signing. The framework sends base claims to your extension, and you return additional claims to merge into the token.
|
|
688
723
|
- **Permission required:** \`extend:identity\`
|
|
689
724
|
- **Hook:** \`useExtendIdentity(handler)\` \u2014 \`ExtendIdentityHandler\` type exported for use with \`useCallback\`
|
|
690
725
|
- **Handler signature:** \`(claims: IdentityBaseClaims) => Record<string, unknown> | Promise<Record<string, unknown>>\`
|
|
@@ -1089,7 +1124,7 @@ const appStore = createStore<AppState>({ viewState: { type: 'menu' } })
|
|
|
1089
1124
|
- \`subscribe(listener: (state: T) => void): () => void\` \u2014 subscribe, returns unsubscribe fn
|
|
1090
1125
|
|
|
1091
1126
|
## useIdentityEvent(eventType, handler)
|
|
1092
|
-
Subscribe to identity events pushed from the host. Requires \`events:identity\` permission and matching entries in manifest \`events\` array.
|
|
1127
|
+
Subscribe to identity events pushed from the host via the framework. Requires \`events:identity\` permission and matching entries in manifest \`events\` array.
|
|
1093
1128
|
- \`eventType: ${identityEventTypes}\`
|
|
1094
1129
|
- \`handler: (event: IdentityEvent) => void\`
|
|
1095
1130
|
- \`IdentityEvent: { eventName: IdentityEventType, data: { state: IdentityState, timestamp: string } }\`
|
|
@@ -1117,7 +1152,7 @@ ${HOOK_SNIPPETS_MEMOIZED["events:messaging"]}
|
|
|
1117
1152
|
\`\`\`
|
|
1118
1153
|
|
|
1119
1154
|
## useActivityEvent(eventType, handler)
|
|
1120
|
-
Subscribe to host
|
|
1155
|
+
Subscribe to activity events pushed from the host via the framework. Requires \`events:activity\` permission and matching entries in manifest \`events\` array.
|
|
1121
1156
|
- \`eventType: ${activityEventTypes} | '*'\` (domain-stripped)
|
|
1122
1157
|
- \`handler: ActivityEventHandler\` \u2014 \`(event: ActivityEvent) => void\`
|
|
1123
1158
|
- \`ActivityEvent: { eventName: string, data: Record<string, unknown> }\`
|
|
@@ -1505,13 +1540,13 @@ These rules must always be followed when writing extension code.
|
|
|
1505
1540
|
|
|
1506
1541
|
## Components
|
|
1507
1542
|
- Use the \`ui.*\` namespace for components (\`<ui.Card>\`, \`<ui.Button>\`) \u2014 don't import components directly
|
|
1508
|
-
- Only use the attributes listed in the component reference \u2014 the
|
|
1543
|
+
- Only use the attributes listed in the component reference \u2014 the framework rejects unknown attributes
|
|
1509
1544
|
- Use \`<ui.ScrollArea>\` for content that may overflow
|
|
1510
1545
|
|
|
1511
1546
|
## Manifest
|
|
1512
1547
|
- Always declare permissions in \`manifest.json\` before using capabilities
|
|
1513
1548
|
- Don't use \`data.fetch\` without adding the domain to \`allowedDomains\` in manifest
|
|
1514
|
-
- \`allowedDomains
|
|
1549
|
+
- \`allowedDomains\`: prefer exact hostnames. Use \`*.<suffix>\` wildcards only if you need any subdomain; the apex is separate. No paths, no protocols, no mid-string wildcards. Wildcards must use a multi-label suffix (e.g., \`*.example.com\`, not \`*.com\`); TLD patterns such as \`*.co.uk\` may pass format checks but will be rejected at submission
|
|
1515
1550
|
|
|
1516
1551
|
## Entry Point
|
|
1517
1552
|
- Don't modify \`index.html\` \u2014 the extension entry point is always \`src/index.tsx\` via \`createExtension\`
|
|
@@ -1673,7 +1708,7 @@ var generateSurfaces = () => {
|
|
|
1673
1708
|
|
|
1674
1709
|
# Surfaces
|
|
1675
1710
|
|
|
1676
|
-
Surfaces are the UI slots where your extension renders content inside the
|
|
1711
|
+
Surfaces are the UI slots where your extension renders content inside the embedding application.
|
|
1677
1712
|
Each surface maps to a specific layout position and is declared as a React component using
|
|
1678
1713
|
the \`<Surface>\` wrapper from \`@stackable-labs/sdk-extension-react\`.
|
|
1679
1714
|
|
|
@@ -1710,15 +1745,15 @@ ${EXAMPLE_SNIPPETS.bootstrap}
|
|
|
1710
1745
|
\`\`\`
|
|
1711
1746
|
|
|
1712
1747
|
\`createExtension\` bootstraps the extension runtime \u2014 it handles the sandboxed iframe
|
|
1713
|
-
communication, capability injection, and surface registration with the
|
|
1748
|
+
communication, capability injection, and surface registration with the framework.
|
|
1714
1749
|
|
|
1715
1750
|
## Surface Lifecycle
|
|
1716
1751
|
|
|
1717
|
-
1. **Mount** \u2014
|
|
1752
|
+
1. **Mount** \u2014 The framework creates an iframe for the extension and loads the entry point
|
|
1718
1753
|
2. **Register** \u2014 \`createExtension\` scans the rendered tree for \`<Surface>\` components
|
|
1719
|
-
3. **Render** \u2014 Each \`<Surface>\` renders its children into the matching
|
|
1754
|
+
3. **Render** \u2014 Each \`<Surface>\` renders its children into the matching slot in the embedding application
|
|
1720
1755
|
4. **Update** \u2014 Surfaces re-render when props, state, or context data changes
|
|
1721
|
-
5. **Unmount** \u2014
|
|
1756
|
+
5. **Unmount** \u2014 The framework removes the extension iframe when no longer needed
|
|
1722
1757
|
|
|
1723
1758
|
## Multi-Surface State
|
|
1724
1759
|
|
|
@@ -1748,31 +1783,32 @@ export const Header = () => {
|
|
|
1748
1783
|
`;
|
|
1749
1784
|
};
|
|
1750
1785
|
var DLX = "pnpm --config.dlx-cache-max-age=0 dlx";
|
|
1786
|
+
var CLI_PKG = "@stackable-labs/cli-app-extension@latest";
|
|
1751
1787
|
var CLI = {
|
|
1752
1788
|
/** Scaffold a new extension project */
|
|
1753
|
-
create: (name = "<extension-name>") => `${DLX}
|
|
1789
|
+
create: (name = "<extension-name>") => `${DLX} ${CLI_PKG} create ${name}`,
|
|
1754
1790
|
/** Start dev servers with hot reload */
|
|
1755
|
-
dev: `${DLX}
|
|
1791
|
+
dev: `${DLX} ${CLI_PKG} dev`,
|
|
1756
1792
|
/** Deploy the extension (future) */
|
|
1757
|
-
deploy: `${DLX}
|
|
1793
|
+
deploy: `${DLX} ${CLI_PKG} deploy`,
|
|
1758
1794
|
/** Validate the extension for common errors (coming soon) */
|
|
1759
|
-
validate: `${DLX}
|
|
1795
|
+
validate: `${DLX} ${CLI_PKG} validate`,
|
|
1760
1796
|
/** Scaffold from an existing extension */
|
|
1761
|
-
scaffold: `${DLX}
|
|
1797
|
+
scaffold: `${DLX} ${CLI_PKG} scaffold`,
|
|
1762
1798
|
/** Update an existing extension */
|
|
1763
|
-
update: `${DLX}
|
|
1799
|
+
update: `${DLX} ${CLI_PKG} update`,
|
|
1764
1800
|
/** Manage CLI authentication (browser-based OAuth) */
|
|
1765
1801
|
auth: {
|
|
1766
|
-
login: `${DLX}
|
|
1767
|
-
logout: `${DLX}
|
|
1768
|
-
status: `${DLX}
|
|
1802
|
+
login: `${DLX} ${CLI_PKG} auth login`,
|
|
1803
|
+
logout: `${DLX} ${CLI_PKG} auth logout`,
|
|
1804
|
+
status: `${DLX} ${CLI_PKG} auth status`
|
|
1769
1805
|
},
|
|
1770
1806
|
/** AI editor configuration tools (Skills + MCP) */
|
|
1771
1807
|
ai: {
|
|
1772
1808
|
/** Download AI editor config files (Stackable Skills) into your project */
|
|
1773
|
-
scaffold: `${DLX}
|
|
1809
|
+
scaffold: `${DLX} ${CLI_PKG} ai scaffold`,
|
|
1774
1810
|
/** Add Stackable MCP server config to your project */
|
|
1775
|
-
mcp: `${DLX}
|
|
1811
|
+
mcp: `${DLX} ${CLI_PKG} ai mcp`
|
|
1776
1812
|
}
|
|
1777
1813
|
};
|
|
1778
1814
|
var TEMPLATE_FLAVOR_META = {
|
|
@@ -1811,7 +1847,7 @@ Stackable supports two authoring paths. Both produce the same kind of extension
|
|
|
1811
1847
|
| **Version control** | Auto-saved to cloud | Git-based |
|
|
1812
1848
|
| **Deployment** | Link to extension + deploy | CLI deploy command |
|
|
1813
1849
|
|
|
1814
|
-
Both produce the same output \u2014 a Stackable extension that runs
|
|
1850
|
+
Both produce the same output \u2014 a Stackable extension that runs inside the embedding application via the same Remote DOM pipeline. **This guide covers the [CLI](/docs/reference/cli-reference) path.** For Studio, see [AI Extension Studio](/docs/reference/extension-studio).
|
|
1815
1851
|
|
|
1816
1852
|
## Prerequisites
|
|
1817
1853
|
|
|
@@ -1851,7 +1887,7 @@ The dev command:
|
|
|
1851
1887
|
2. Creates a public Cloudflare tunnel to your local server
|
|
1852
1888
|
3. Displays a **query parameter** you can use to preview your extension
|
|
1853
1889
|
|
|
1854
|
-
### Testing against your
|
|
1890
|
+
### Testing against your extension
|
|
1855
1891
|
|
|
1856
1892
|
The CLI outputs a query param like:
|
|
1857
1893
|
|
|
@@ -1859,16 +1895,17 @@ The CLI outputs a query param like:
|
|
|
1859
1895
|
?_stackable_dev=ext-123%3Ahttps%3A%2F%2Fabc.trycloudflare.com
|
|
1860
1896
|
\`\`\`
|
|
1861
1897
|
|
|
1862
|
-
Copy this and **append it to
|
|
1863
|
-
|
|
1898
|
+
Copy this and **append it to the host site's URL** (the site or product where your
|
|
1899
|
+
extension is installed/authorized) to load your local extension instead of the
|
|
1900
|
+
production bundle. For example:
|
|
1864
1901
|
|
|
1865
1902
|
\`\`\`
|
|
1866
|
-
https://your-
|
|
1903
|
+
https://your-host-site.com/dashboard?_stackable_dev=ext-123%3Ahttps%3A%2F%2Fabc.trycloudflare.com
|
|
1867
1904
|
\`\`\`
|
|
1868
1905
|
|
|
1869
1906
|
This override is **browser-session only** \u2014 no database changes, no shared state.
|
|
1870
1907
|
Each developer gets isolated overrides. Changes you make locally appear immediately
|
|
1871
|
-
in
|
|
1908
|
+
in your extension via hot reload.
|
|
1872
1909
|
|
|
1873
1910
|
Use \`--no-tunnel\` if you only want to run the local Vite dev server without a tunnel.
|
|
1874
1911
|
|
|
@@ -2040,9 +2077,9 @@ ${CLI.dev}
|
|
|
2040
2077
|
1. Reads \`.env.stackable\` for cached App/Extension context (prompts if missing)
|
|
2041
2078
|
2. Creates Cloudflare tunnels for the extension dev server
|
|
2042
2079
|
3. Starts Vite dev servers with hot reload
|
|
2043
|
-
4. Displays a \`_stackable_dev\` query param to append to
|
|
2080
|
+
4. Displays a \`_stackable_dev\` query param to append to a host site's URL
|
|
2044
2081
|
|
|
2045
|
-
### Host
|
|
2082
|
+
### Host-Site Override
|
|
2046
2083
|
|
|
2047
2084
|
The CLI outputs a query param like:
|
|
2048
2085
|
|
|
@@ -2050,9 +2087,10 @@ The CLI outputs a query param like:
|
|
|
2050
2087
|
?_stackable_dev=ext-123%3Ahttps%3A%2F%2Fabc.trycloudflare.com
|
|
2051
2088
|
\`\`\`
|
|
2052
2089
|
|
|
2053
|
-
Append this to your deployed host
|
|
2054
|
-
|
|
2055
|
-
|
|
2090
|
+
Append this to your deployed host site's URL (the site or product where your
|
|
2091
|
+
extension is installed/authorized) to load your local extension instead of the production
|
|
2092
|
+
bundle. The override is browser-session only \u2014 no DB changes, no shared state.
|
|
2093
|
+
Each developer gets isolated overrides.
|
|
2056
2094
|
|
|
2057
2095
|
## validate *(coming soon)*
|
|
2058
2096
|
|
|
@@ -2194,7 +2232,7 @@ var generateExternalApis = () => {
|
|
|
2194
2232
|
const fm = frontmatter({
|
|
2195
2233
|
root: false,
|
|
2196
2234
|
targets: ["*"],
|
|
2197
|
-
description: "Direct HTTP requests via data.fetch, allowedDomains configuration, and API wrapper patterns",
|
|
2235
|
+
description: "Direct HTTP requests via data.fetch, allowedDomains configuration, wildcard domains, and API wrapper patterns",
|
|
2198
2236
|
globs: ["packages/extension/src/**/*.tsx", "packages/extension/src/**/*.ts", "packages/extension/public/manifest.json"]
|
|
2199
2237
|
});
|
|
2200
2238
|
return `${fm}
|
|
@@ -2202,7 +2240,7 @@ var generateExternalApis = () => {
|
|
|
2202
2240
|
# External APIs
|
|
2203
2241
|
|
|
2204
2242
|
Extensions can make direct HTTP requests to external services using the \`data.fetch\`
|
|
2205
|
-
capability. All requests are proxied through the
|
|
2243
|
+
capability. All requests are proxied through the framework for security \u2014 the extension
|
|
2206
2244
|
never makes raw network calls from the sandbox.
|
|
2207
2245
|
|
|
2208
2246
|
## Setup
|
|
@@ -2223,14 +2261,75 @@ Every domain your extension calls must be listed in \`allowedDomains\`:
|
|
|
2223
2261
|
|
|
2224
2262
|
\`\`\`json
|
|
2225
2263
|
{
|
|
2226
|
-
"allowedDomains": ["api.example.com", "graphql.example.com"]
|
|
2264
|
+
"allowedDomains": ["api.example.com", "graphql.example.com", "*.myshopify.com"]
|
|
2227
2265
|
}
|
|
2228
2266
|
\`\`\`
|
|
2229
2267
|
|
|
2230
2268
|
**Rules:**
|
|
2231
|
-
- Exact hostnames
|
|
2232
|
-
-
|
|
2233
|
-
-
|
|
2269
|
+
- Exact hostnames or \`*.<suffix>\` wildcards (subdomain match) \u2014 no paths, no protocols
|
|
2270
|
+
- Wildcards take the form \`*.<suffix>\` and must use a multi-label suffix (e.g., \`*.example.com\`, not \`*.com\`)
|
|
2271
|
+
- The framework will reject requests to unlisted domains
|
|
2272
|
+
- Add each subdomain separately when listing exact hosts (e.g., \`api.example.com\` and \`cdn.example.com\`)
|
|
2273
|
+
|
|
2274
|
+
### Wildcards
|
|
2275
|
+
|
|
2276
|
+
**Prefer exact hostnames where possible.** Use \`*.example.com\` only when you
|
|
2277
|
+
need to match any subdomain \u2014 for example, tenant-per-subdomain platforms
|
|
2278
|
+
(Shopify shops, Salesforce subdomains, Zendesk subdomains, multi-region API hosts) where
|
|
2279
|
+
you don't know the full set of hostnames at authoring time or will differ by instance.
|
|
2280
|
+
|
|
2281
|
+
\`\`\`json
|
|
2282
|
+
{
|
|
2283
|
+
"allowedDomains": ["*.myshopify.com"]
|
|
2284
|
+
}
|
|
2285
|
+
\`\`\`
|
|
2286
|
+
|
|
2287
|
+
With this entry, the framework allows requests to \`acme.myshopify.com\`,
|
|
2288
|
+
\`widgets.myshopify.com\`, and any subdomain at any depth (e.g.,
|
|
2289
|
+
\`shop.eu.myshopify.com\`). A request to \`evil.com\` is still rejected.
|
|
2290
|
+
|
|
2291
|
+
**Apex is separate.** \`*.myshopify.com\` does **not** match \`myshopify.com\`
|
|
2292
|
+
itself \u2014 this matches CORS, TLS-certificate, and DNS-wildcard convention. To
|
|
2293
|
+
allow both the apex and any subdomain, list both:
|
|
2294
|
+
|
|
2295
|
+
\`\`\`json
|
|
2296
|
+
{
|
|
2297
|
+
"allowedDomains": ["myshopify.com", "*.myshopify.com"]
|
|
2298
|
+
}
|
|
2299
|
+
\`\`\`
|
|
2300
|
+
|
|
2301
|
+
**Worked example:**
|
|
2302
|
+
|
|
2303
|
+
\`\`\`json
|
|
2304
|
+
{
|
|
2305
|
+
"allowedDomains": [
|
|
2306
|
+
"*.myshopify.com",
|
|
2307
|
+
"myshopify.com",
|
|
2308
|
+
"api.example.com",
|
|
2309
|
+
"*.staging.example.com"
|
|
2310
|
+
]
|
|
2311
|
+
}
|
|
2312
|
+
\`\`\`
|
|
2313
|
+
|
|
2314
|
+
This allows any customer shop subdomain, the Shopify apex, an exact API host,
|
|
2315
|
+
and any subdomain (at any depth) under \`staging.example.com\`.
|
|
2316
|
+
|
|
2317
|
+
**What's rejected and where.** Wildcards must use a multi-label suffix
|
|
2318
|
+
(e.g., \`*.example.com\`, not \`*.com\`). Two layers enforce this:
|
|
2319
|
+
|
|
2320
|
+
- *Format-level* (instant feedback in the CLI, MCP, and Studio AI panel) \u2014
|
|
2321
|
+
\`*\` alone, multiple wildcards, mid-string wildcards (\`api-*.example.com\`),
|
|
2322
|
+
single-label suffixes (\`*.com\`, \`*.localhost\`, \`*.io\`), protocols
|
|
2323
|
+
(\`https://...\`), paths, ports, and IP literals.
|
|
2324
|
+
- *Server-level* (at submission) \u2014 TLD patterns such as \`*.co.uk\`,
|
|
2325
|
+
\`*.com.au\`, and \`*.github.io\` may pass the format check but will be
|
|
2326
|
+
rejected at submission, since they would otherwise allow any registrant
|
|
2327
|
+
under a shared registry.
|
|
2328
|
+
|
|
2329
|
+
**Local development.** Exact \`localhost\` and IPv4 literals (\`127.0.0.1\`) are
|
|
2330
|
+
still valid for exact-host entries. Dev/staging extension modes bypass the
|
|
2331
|
+
domain check entirely, so wildcards aren't needed for local iteration \u2014 they
|
|
2332
|
+
matter at submission and in production.
|
|
2234
2333
|
|
|
2235
2334
|
### 3. Make Requests
|
|
2236
2335
|
|
|
@@ -2317,10 +2416,10 @@ const customer = await fetchCustomer(capabilities, customerId)
|
|
|
2317
2416
|
|
|
2318
2417
|
| | data.fetch | data.query |
|
|
2319
2418
|
|--|-----------|-----------|
|
|
2320
|
-
| **Who handles the request** | Extension (via proxy) |
|
|
2419
|
+
| **Who handles the request** | Extension (via proxy) | Platform |
|
|
2321
2420
|
| **Permission** | \`data:fetch\` | \`data:query\` |
|
|
2322
2421
|
| **Domain config** | Required (\`allowedDomains\`) | Not needed |
|
|
2323
|
-
| **Use when** | Calling external APIs directly |
|
|
2422
|
+
| **Use when** | Calling external APIs directly | Platform provides the API integration |
|
|
2324
2423
|
|
|
2325
2424
|
## Error Handling
|
|
2326
2425
|
|
|
@@ -2379,7 +2478,7 @@ AI-powered smart insertion to place the component in the correct location.
|
|
|
2379
2478
|
|
|
2380
2479
|
### Surfaces
|
|
2381
2480
|
|
|
2382
|
-
Lists the surface targets available for your
|
|
2481
|
+
Lists the surface targets available for your extension (e.g., \`slot.header\`,
|
|
2383
2482
|
\`slot.content\`, \`slot.footer\`). Surfaces already present in your code are
|
|
2384
2483
|
filtered out. Clicking a surface adds it to your manifest and inserts a
|
|
2385
2484
|
\`<Surface>\` block via AI smart insertion.
|
|
@@ -2448,7 +2547,7 @@ Studio toolbar.
|
|
|
2448
2547
|
|
|
2449
2548
|
Comparing approaches? See [Choosing your path](/docs/guides/quick-start#choosing-your-path) in the Quick Start guide for the full Studio vs CLI comparison.
|
|
2450
2549
|
|
|
2451
|
-
Both workflows produce the same output \u2014 a Stackable extension that runs
|
|
2550
|
+
Both workflows produce the same output \u2014 a Stackable extension that runs inside the embedding application via the same Remote DOM pipeline. Start in Studio to prototype, then scaffold to CLI when you need the full development workflow.
|
|
2452
2551
|
`;
|
|
2453
2552
|
};
|
|
2454
2553
|
|
|
@@ -2599,7 +2698,7 @@ my-extension/
|
|
|
2599
2698
|
|
|
2600
2699
|
### manifest.json
|
|
2601
2700
|
|
|
2602
|
-
The extension manifest declares what the extension needs from the
|
|
2701
|
+
The extension manifest declares what the extension needs from the framework:
|
|
2603
2702
|
|
|
2604
2703
|
\`\`\`json
|
|
2605
2704
|
{
|
|
@@ -2617,7 +2716,11 @@ The extension manifest declares what the extension needs from the host:
|
|
|
2617
2716
|
| \`version\` | Semver version string |
|
|
2618
2717
|
| \`targets\` | Surface slots the extension renders into |
|
|
2619
2718
|
| \`permissions\` | Capabilities the extension uses |
|
|
2620
|
-
| \`allowedDomains\` | Hostnames for data.fetch requests (exact
|
|
2719
|
+
| \`allowedDomains\` | Hostnames for data.fetch requests (exact hostnames or \`*.<suffix>\` wildcards) |
|
|
2720
|
+
|
|
2721
|
+
See **External APIs > Wildcards** for the full \`allowedDomains\` rules,
|
|
2722
|
+
including the apex-vs-subdomain distinction and what's rejected at format
|
|
2723
|
+
vs. submission time.
|
|
2621
2724
|
|
|
2622
2725
|
### index.tsx \u2014 Entry Point
|
|
2623
2726
|
|
|
@@ -2628,9 +2731,9 @@ ${EXAMPLE_SNIPPETS.bootstrap}
|
|
|
2628
2731
|
\`\`\`
|
|
2629
2732
|
|
|
2630
2733
|
\`createExtension\` handles:
|
|
2631
|
-
- Sandboxed iframe communication with the
|
|
2734
|
+
- Sandboxed iframe communication with the framework
|
|
2632
2735
|
- Capability injection (makes \`useCapabilities()\` work)
|
|
2633
|
-
- Surface registration (maps \`<Surface id="...">\` to
|
|
2736
|
+
- Surface registration (maps \`<Surface id="...">\` to layout slots in the embedding application)
|
|
2634
2737
|
|
|
2635
2738
|
**Do not modify \`index.html\`** \u2014 the extension always bootstraps through \`createExtension\`.
|
|
2636
2739
|
|
|
@@ -2690,17 +2793,17 @@ var generateStylingAndTheming = () => {
|
|
|
2690
2793
|
|
|
2691
2794
|
# Styling & Theming
|
|
2692
2795
|
|
|
2693
|
-
Extensions inherit the
|
|
2694
|
-
(\`ui.*\` namespace) renders inside the
|
|
2695
|
-
|
|
2796
|
+
Extensions inherit the embedding application's theme automatically. The SDK component
|
|
2797
|
+
library (\`ui.*\` namespace) renders inside the surrounding application's styling context,
|
|
2798
|
+
so colors, fonts, and spacing match without any configuration.
|
|
2696
2799
|
|
|
2697
|
-
##
|
|
2800
|
+
## Theme Inheritance
|
|
2698
2801
|
|
|
2699
|
-
The \`ui.*\` components automatically use the
|
|
2700
|
-
- **Colors** \u2014 text, backgrounds, borders adapt to the
|
|
2701
|
-
- **Typography** \u2014 font family, sizes, and weights match the
|
|
2702
|
-
- **Spacing** \u2014 padding and margins follow the
|
|
2703
|
-
- **Dark mode** \u2014 components respond to the
|
|
2802
|
+
The \`ui.*\` components automatically use the embedding application's design tokens:
|
|
2803
|
+
- **Colors** \u2014 text, backgrounds, borders adapt to the application's color scheme
|
|
2804
|
+
- **Typography** \u2014 font family, sizes, and weights match the application
|
|
2805
|
+
- **Spacing** \u2014 padding and margins follow the application's spacing scale
|
|
2806
|
+
- **Dark mode** \u2014 components respond to the application's light/dark mode setting
|
|
2704
2807
|
|
|
2705
2808
|
No CSS or theme configuration is needed in the extension.
|
|
2706
2809
|
|
|
@@ -2772,7 +2875,7 @@ Use component props (not CSS) to control visual style:
|
|
|
2772
2875
|
|
|
2773
2876
|
Extensions run in a sandboxed iframe. These constraints apply:
|
|
2774
2877
|
|
|
2775
|
-
- **No global CSS** \u2014 styles don't leak between extensions or into the
|
|
2878
|
+
- **No global CSS** \u2014 styles don't leak between extensions or into the embedding application
|
|
2776
2879
|
- **No \`document\` access** \u2014 cannot inject stylesheets or modify the DOM directly
|
|
2777
2880
|
- **No \`window.location\`** \u2014 cannot read or modify the URL
|
|
2778
2881
|
- **Component-only styling** \u2014 use \`className\` on \`ui.*\` components, not raw HTML elements
|
|
@@ -2780,7 +2883,7 @@ Extensions run in a sandboxed iframe. These constraints apply:
|
|
|
2780
2883
|
|
|
2781
2884
|
## Responsive Design
|
|
2782
2885
|
|
|
2783
|
-
Extensions render in a constrained viewport (the
|
|
2886
|
+
Extensions render in a constrained viewport (the embedding application's sidebar or panel). Design for
|
|
2784
2887
|
narrow widths:
|
|
2785
2888
|
|
|
2786
2889
|
\`\`\`tsx
|
|
@@ -2797,7 +2900,7 @@ narrow widths:
|
|
|
2797
2900
|
- Assume a **narrow viewport** (~300-400px wide)
|
|
2798
2901
|
- Use \`<ui.ScrollArea>\` for long lists or content
|
|
2799
2902
|
- Avoid horizontal scrolling \u2014 stack elements vertically
|
|
2800
|
-
- Test at the minimum panel width the
|
|
2903
|
+
- Test at the minimum panel width the embedding application supports
|
|
2801
2904
|
`;
|
|
2802
2905
|
};
|
|
2803
2906
|
|
|
@@ -2885,16 +2988,36 @@ var parseSafelist = (css) => {
|
|
|
2885
2988
|
return classes;
|
|
2886
2989
|
};
|
|
2887
2990
|
var classifyFamily = (cls) => {
|
|
2888
|
-
if (cls.startsWith("dark:hover:text-"))
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
if (cls.startsWith("dark:bg-"))
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
if (cls.startsWith("
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
if (cls.startsWith("
|
|
2991
|
+
if (cls.startsWith("dark:hover:text-")) {
|
|
2992
|
+
return "Dark \u2014 hover text";
|
|
2993
|
+
}
|
|
2994
|
+
if (cls.startsWith("dark:hover:bg-")) {
|
|
2995
|
+
return "Dark \u2014 hover background";
|
|
2996
|
+
}
|
|
2997
|
+
if (cls.startsWith("dark:text-")) {
|
|
2998
|
+
return "Dark \u2014 text color";
|
|
2999
|
+
}
|
|
3000
|
+
if (cls.startsWith("dark:bg-")) {
|
|
3001
|
+
return "Dark \u2014 background color";
|
|
3002
|
+
}
|
|
3003
|
+
if (cls.startsWith("dark:border-")) {
|
|
3004
|
+
return "Dark \u2014 border color";
|
|
3005
|
+
}
|
|
3006
|
+
if (cls.startsWith("hover:opacity-")) {
|
|
3007
|
+
return "Hover \u2014 opacity";
|
|
3008
|
+
}
|
|
3009
|
+
if (cls.startsWith("hover:text-")) {
|
|
3010
|
+
return "Hover \u2014 text color";
|
|
3011
|
+
}
|
|
3012
|
+
if (cls.startsWith("hover:bg-")) {
|
|
3013
|
+
return "Hover \u2014 background color";
|
|
3014
|
+
}
|
|
3015
|
+
if (cls.startsWith("focus:")) {
|
|
3016
|
+
return "Focus";
|
|
3017
|
+
}
|
|
3018
|
+
if (cls.startsWith("disabled:")) {
|
|
3019
|
+
return "Disabled";
|
|
3020
|
+
}
|
|
2898
3021
|
if (/^(m|mx|my|mt|mb|ml|mr)-/.test(cls)) {
|
|
2899
3022
|
return "Margin";
|
|
2900
3023
|
}
|
|
@@ -3103,14 +3226,14 @@ Apply them via the \`className\` prop on \`ui.*\` components:
|
|
|
3103
3226
|
Always prefer:
|
|
3104
3227
|
- Component variants (\`<ui.Button variant="primary">\`) over color overrides
|
|
3105
3228
|
- Layout components (\`<ui.Stack>\`, \`<ui.Inline>\`) over manual flexbox class chains
|
|
3106
|
-
- The semantic color tokens (\`text-foreground\`, \`bg-background\`) which adapt to the
|
|
3229
|
+
- The semantic color tokens (\`text-foreground\`, \`bg-background\`) which adapt to the embedding application's theme
|
|
3107
3230
|
|
|
3108
3231
|
## Safelist limits
|
|
3109
3232
|
|
|
3110
3233
|
The safelist is intentionally a fixed list, **NOT the full Tailwind catalog** to provide UI consistency and reduce bundle size. Anything beyond what is listed below will be missing from the platform stylesheet at runtime \u2014 the class will appear in your DOM but the corresponding CSS rule will not exist, and the style will silently no-op. In particular:
|
|
3111
3234
|
|
|
3112
3235
|
- **Arbitrary values are NOT supported** in general (e.g. \`w-[123px]\`, \`bg-[#1a1a1a]\`, \`text-[15px]\`). The single exception is \`aspect-[9/16]\` (portrait video). For any other arbitrary value, use the named utility closest to your target.
|
|
3113
|
-
- **Off-list color shades** (e.g. \`bg-cyan-500\`, \`text-rose-500\`) are not pre-emitted. The supported color set below is curated for visual consistency with the
|
|
3236
|
+
- **Off-list color shades** (e.g. \`bg-cyan-500\`, \`text-rose-500\`) are not pre-emitted. The supported color set below is curated for visual consistency with the embedding application's theme.
|
|
3114
3237
|
- **Off-list ladder values** (e.g. \`mt-32\`, \`text-4xl\`, \`w-128\`) are not pre-emitted. The ladders below cover sizes appropriate for the embedded widget's narrow viewport.
|
|
3115
3238
|
|
|
3116
3239
|
If you need something that's not on this list, file an issue with your use case \u2014 the safelist is curated, not frozen.
|
|
@@ -3263,9 +3386,9 @@ Focused examples showing each SDK capability in a working Surface component.
|
|
|
3263
3386
|
Each example is self-contained \u2014 copy it into a surface file and add the
|
|
3264
3387
|
corresponding permission to your \`manifest.json\`.
|
|
3265
3388
|
|
|
3266
|
-
## context.read \u2014 Reading
|
|
3389
|
+
## context.read \u2014 Reading Platform Context
|
|
3267
3390
|
|
|
3268
|
-
Read customer and session data provided by the
|
|
3391
|
+
Read customer and session data provided by the framework. The \`useContextData\`
|
|
3269
3392
|
hook handles loading state automatically.
|
|
3270
3393
|
|
|
3271
3394
|
**Permission:** \`context:read\`
|
|
@@ -3274,9 +3397,9 @@ hook handles loading state automatically.
|
|
|
3274
3397
|
${EXAMPLE_SNIPPETS["context.read"]}
|
|
3275
3398
|
\`\`\`
|
|
3276
3399
|
|
|
3277
|
-
## data.query \u2014
|
|
3400
|
+
## data.query \u2014 Platform-Mediated Requests
|
|
3278
3401
|
|
|
3279
|
-
Send structured requests to the
|
|
3402
|
+
Send structured requests to the platform. The framework handles the API
|
|
3280
3403
|
call and returns the result \u2014 no \`allowedDomains\` needed.
|
|
3281
3404
|
|
|
3282
3405
|
**Permission:** \`data:query\`
|
|
@@ -3288,7 +3411,8 @@ ${EXAMPLE_SNIPPETS["data.query"]}
|
|
|
3288
3411
|
## data.fetch \u2014 Direct HTTP Requests
|
|
3289
3412
|
|
|
3290
3413
|
Make HTTP requests directly from the extension sandbox. The domain must be
|
|
3291
|
-
listed in \`allowedDomains\` in your manifest
|
|
3414
|
+
listed in \`allowedDomains\` in your manifest \u2014 exact hostnames or
|
|
3415
|
+
\`*.<suffix>\` wildcards. See **External APIs > Wildcards** for the full rules.
|
|
3292
3416
|
|
|
3293
3417
|
**Permission:** \`data:fetch\`
|
|
3294
3418
|
|
|
@@ -3298,7 +3422,7 @@ ${EXAMPLE_SNIPPETS["data.fetch"]}
|
|
|
3298
3422
|
|
|
3299
3423
|
## actions.toast \u2014 Toast Notifications
|
|
3300
3424
|
|
|
3301
|
-
Display toast notifications in the host UI to provide feedback to users.
|
|
3425
|
+
Display toast notifications in the host widget's UI to provide feedback to users.
|
|
3302
3426
|
|
|
3303
3427
|
**Permission:** \`actions:toast\`
|
|
3304
3428
|
|
|
@@ -3335,7 +3459,7 @@ surfaces, store-based navigation, and loading states.
|
|
|
3335
3459
|
## Bootstrap \u2014 Entry Point
|
|
3336
3460
|
|
|
3337
3461
|
Set up your extension entry point in \`src/index.tsx\`. The \`createExtension\` factory
|
|
3338
|
-
bootstraps the sandboxed runtime and registers all surfaces with the
|
|
3462
|
+
bootstraps the sandboxed runtime and registers all surfaces with the framework.
|
|
3339
3463
|
|
|
3340
3464
|
\`\`\`tsx
|
|
3341
3465
|
${EXAMPLE_SNIPPETS.bootstrap}
|
|
@@ -3343,7 +3467,7 @@ ${EXAMPLE_SNIPPETS.bootstrap}
|
|
|
3343
3467
|
|
|
3344
3468
|
## Surfaces \u2014 Declaring UI Slots
|
|
3345
3469
|
|
|
3346
|
-
Each surface renders into a specific layout slot in the
|
|
3470
|
+
Each surface renders into a specific layout slot in the embedding application.
|
|
3347
3471
|
The \`id\` prop must match a target declared in your \`manifest.json\`.
|
|
3348
3472
|
|
|
3349
3473
|
### Header
|
|
@@ -3412,7 +3536,7 @@ var generateCookbookEvents = () => {
|
|
|
3412
3536
|
|
|
3413
3537
|
# Events & Extensions
|
|
3414
3538
|
|
|
3415
|
-
Subscribe to real-time events pushed from the host and extend identity claims.
|
|
3539
|
+
Subscribe to real-time events pushed from the host via the framework, and extend identity claims.
|
|
3416
3540
|
Each event type has a dedicated hook \u2014 never use \`capabilities.events.*\` directly.
|
|
3417
3541
|
|
|
3418
3542
|
## Identity Events
|
|
@@ -3456,9 +3580,9 @@ ${EXAMPLE_SNIPPETS["events:messaging"]}
|
|
|
3456
3580
|
|
|
3457
3581
|
## Activity Events
|
|
3458
3582
|
|
|
3459
|
-
Subscribe to host activity events like page views, clicks, and purchases
|
|
3460
|
-
Activity event names are domain-stripped \u2014
|
|
3461
|
-
not \`'activity:product_view'\`.
|
|
3583
|
+
Subscribe to host site activity events like page views, clicks, and purchases pushed from
|
|
3584
|
+
the host via the framework. Activity event names are domain-stripped \u2014
|
|
3585
|
+
use \`useActivityEvent('product_view', ...)\` not \`'activity:product_view'\`.
|
|
3462
3586
|
|
|
3463
3587
|
**Permission:** \`events:activity\`
|
|
3464
3588
|
**Well-known events:** ${activityEventTypes2}
|
|
@@ -4769,12 +4893,6 @@ pair(EXAMPLE_SNIPPETS, EXAMPLE_SNIPPETS2);
|
|
|
4769
4893
|
pair(CAPABILITY_SNIPPETS, CAPABILITY_SNIPPETS2);
|
|
4770
4894
|
pair(EVENT_SNIPPETS, EVENT_SNIPPETS2);
|
|
4771
4895
|
|
|
4772
|
-
// ../../lib/contracts/src/custom.ts
|
|
4773
|
-
var CUSTOM_ROLE = {
|
|
4774
|
-
SUPER_ADMIN: "super_admin"
|
|
4775
|
-
};
|
|
4776
|
-
new Set(Object.values(CUSTOM_ROLE));
|
|
4777
|
-
|
|
4778
4896
|
// ../../lib/utils-js/src/crypto.ts
|
|
4779
4897
|
var getCrypto = () => {
|
|
4780
4898
|
if (typeof globalThis !== "undefined" && globalThis.crypto) {
|
|
@@ -5005,13 +5123,21 @@ var createMcpServer = (options = {}) => {
|
|
|
5005
5123
|
}
|
|
5006
5124
|
if (!Array.isArray(manifest.allowedDomains)) {
|
|
5007
5125
|
errors.push('Missing "allowedDomains" array. Use an empty array if no external API calls are needed.');
|
|
5126
|
+
} else {
|
|
5127
|
+
for (let i = 0; i < manifest.allowedDomains.length; i++) {
|
|
5128
|
+
const entry = manifest.allowedDomains[i];
|
|
5129
|
+
const reason = validatePatternFormat(entry);
|
|
5130
|
+
if (reason) {
|
|
5131
|
+
errors.push(`allowedDomains[${i}] "${String(entry)}": ${reason}`);
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5008
5134
|
}
|
|
5009
5135
|
const permissions = manifest.permissions ?? [];
|
|
5010
5136
|
if (permissions.includes("data:fetch") && Array.isArray(manifest.allowedDomains) && manifest.allowedDomains.length === 0) {
|
|
5011
5137
|
errors.push('"data:fetch" permission declared but "allowedDomains" is empty. Add the domains your extension needs to fetch from.');
|
|
5012
5138
|
}
|
|
5013
5139
|
if (errors.length === 0) {
|
|
5014
|
-
return textContent("Manifest is valid.");
|
|
5140
|
+
return textContent("Manifest is valid. Note: server will additionally validate wildcard suffixes against the public suffix list at submission time.");
|
|
5015
5141
|
}
|
|
5016
5142
|
return errorContent(`Manifest validation failed:
|
|
5017
5143
|
${errors.map((e) => `- ${e}`).join("\n")}`);
|