@stackable-labs/mcp-app-extension 0.7.2 → 0.8.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 +52 -11
- package/dist/server.js +52 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -463,8 +463,8 @@ const result = await capabilities.data.query<Customer>({
|
|
|
463
463
|
})
|
|
464
464
|
\`\`\`
|
|
465
465
|
|
|
466
|
-
## data.fetch \u2014
|
|
467
|
-
|
|
466
|
+
## data.fetch \u2014 HTTP Requests to External APIs
|
|
467
|
+
Make HTTP requests to external APIs. Domain must be in \`allowedDomains\` in manifest. Requests are proxied through the Stackable platform server.
|
|
468
468
|
- **Permission required:** \`data:fetch\`
|
|
469
469
|
- **Usage:** \`capabilities.data.fetch(url: string, init?: FetchRequestInit): Promise<FetchResponse>\`
|
|
470
470
|
- **FetchRequestInit:** \`{ method?: 'GET'|'POST'|'PUT'|'PATCH'|'DELETE', headers?: Record<string,string>, body?: unknown }\`
|
|
@@ -480,21 +480,52 @@ if (!result.ok) throw new Error(\`Request failed: \${result.status}\`)
|
|
|
480
480
|
const data = result.data as MyType
|
|
481
481
|
\`\`\`
|
|
482
482
|
|
|
483
|
+
### Secret injection via \`{{settings.xxx}}\` placeholders
|
|
484
|
+
For API keys and tokens stored as \`secret: true\` fields in \`settingsSchema\`, use template placeholders in header values. The proxy resolves them server-side \u2014 **the real secret never enters extension code**.
|
|
485
|
+
|
|
486
|
+
\`\`\`tsx
|
|
487
|
+
const result = await capabilities.data.fetch('https://api.example.com/orders', {
|
|
488
|
+
method: 'GET',
|
|
489
|
+
headers: {
|
|
490
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
491
|
+
'Authorization': 'Bearer {{settings.token}}',
|
|
492
|
+
},
|
|
493
|
+
})
|
|
494
|
+
\`\`\`
|
|
495
|
+
|
|
496
|
+
- Placeholders are only allowed in **header values** (not URLs, header names, or body)
|
|
497
|
+
- For \`required: true\` secret fields, the proxy returns 400 if the value is not configured
|
|
498
|
+
- For optional secret fields, the entire header is omitted if the value is not configured
|
|
499
|
+
- Declare secret fields in your \`manifest.json\` \`settingsSchema\` with \`"secret": true\`
|
|
500
|
+
|
|
483
501
|
## context.read \u2014 Read Host Context
|
|
484
|
-
Read host-provided context (customer ID, email, etc.).
|
|
502
|
+
Read host-provided context (customer ID, email, extension settings, etc.).
|
|
485
503
|
- **Permission required:** \`context:read\`
|
|
486
504
|
- **Usage:** \`capabilities.context.read(): Promise<ContextData>\`
|
|
487
|
-
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, [key: string]: unknown }\`
|
|
488
|
-
- **Convenience
|
|
505
|
+
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, settings?: Record<string, unknown>, [key: string]: unknown }\`
|
|
506
|
+
- **Convenience hooks:**
|
|
507
|
+
- \`useContextData()\` returns \`ContextData & { loading: boolean }\`
|
|
508
|
+
- \`useSettings()\` returns \`Record<string, unknown>\` \u2014 shorthand for \`contextData.settings ?? {}\`
|
|
489
509
|
|
|
490
510
|
\`\`\`tsx
|
|
491
|
-
//
|
|
492
|
-
const { loading, customerId, customerEmail } = useContextData()
|
|
511
|
+
// Read all context (customer + settings)
|
|
512
|
+
const { loading, customerId, customerEmail, settings } = useContextData()
|
|
513
|
+
|
|
514
|
+
// Read only extension settings (convenience)
|
|
515
|
+
const settings = useSettings()
|
|
516
|
+
const apiBaseUrl = settings.baseUrl as string
|
|
493
517
|
|
|
494
518
|
// Alternative: use the capability directly
|
|
495
519
|
const context = await capabilities.context.read()
|
|
496
520
|
\`\`\`
|
|
497
521
|
|
|
522
|
+
### Extension settings in context
|
|
523
|
+
Non-secret settings declared in \`settingsSchema\` are automatically available via \`contextData.settings\`. Values are scoped to the calling extension on the current instance \u2014 an extension never sees other extensions' settings.
|
|
524
|
+
|
|
525
|
+
- **Secret fields are never included** in context \u2014 use \`{{settings.xxx}}\` placeholders in \`data.fetch\` headers instead
|
|
526
|
+
- Settings propagate on **page load** \u2014 changes made in the admin dashboard take effect on the next page reload, not mid-session
|
|
527
|
+
- No new permission needed \u2014 \`context:read\` is the only gate
|
|
528
|
+
|
|
498
529
|
## actions.toast \u2014 Show Toast Notifications
|
|
499
530
|
Display a toast notification in the host UI.
|
|
500
531
|
- **Permission required:** \`actions:toast\`
|
|
@@ -977,9 +1008,16 @@ const viewState = useStore(appStore, (s) => s.viewState)
|
|
|
977
1008
|
\`\`\`
|
|
978
1009
|
|
|
979
1010
|
## useContextData()
|
|
980
|
-
Reads host-provided context. Returns \`{ loading, customerId, customerEmail, ... }\`.
|
|
1011
|
+
Reads host-provided context including extension settings. Returns \`{ loading, customerId, customerEmail, settings, ... }\`.
|
|
1012
|
+
\`\`\`tsx
|
|
1013
|
+
const { loading, customerId, customerEmail, settings } = useContextData()
|
|
1014
|
+
\`\`\`
|
|
1015
|
+
|
|
1016
|
+
## useSettings()
|
|
1017
|
+
Convenience hook for reading extension settings. Returns non-secret settings scoped to this extension on this instance.
|
|
981
1018
|
\`\`\`tsx
|
|
982
|
-
const
|
|
1019
|
+
const settings = useSettings()
|
|
1020
|
+
const apiBaseUrl = settings.baseUrl as string
|
|
983
1021
|
\`\`\`
|
|
984
1022
|
|
|
985
1023
|
## useSurfaceContext()
|
|
@@ -3254,8 +3292,11 @@ const capabilities = useCapabilities()
|
|
|
3254
3292
|
const response = await capabilities.data.fetch('https://api.example.com/endpoint')
|
|
3255
3293
|
`,
|
|
3256
3294
|
"context.read": `
|
|
3257
|
-
|
|
3258
|
-
const
|
|
3295
|
+
// Read host context + extension settings
|
|
3296
|
+
const { customerId, settings } = useContextData()
|
|
3297
|
+
|
|
3298
|
+
// Or use the convenience hook for settings only
|
|
3299
|
+
const settings = useSettings()
|
|
3259
3300
|
`,
|
|
3260
3301
|
"actions.toast": `
|
|
3261
3302
|
const capabilities = useCapabilities()
|
package/dist/server.js
CHANGED
|
@@ -461,8 +461,8 @@ const result = await capabilities.data.query<Customer>({
|
|
|
461
461
|
})
|
|
462
462
|
\`\`\`
|
|
463
463
|
|
|
464
|
-
## data.fetch \u2014
|
|
465
|
-
|
|
464
|
+
## data.fetch \u2014 HTTP Requests to External APIs
|
|
465
|
+
Make HTTP requests to external APIs. Domain must be in \`allowedDomains\` in manifest. Requests are proxied through the Stackable platform server.
|
|
466
466
|
- **Permission required:** \`data:fetch\`
|
|
467
467
|
- **Usage:** \`capabilities.data.fetch(url: string, init?: FetchRequestInit): Promise<FetchResponse>\`
|
|
468
468
|
- **FetchRequestInit:** \`{ method?: 'GET'|'POST'|'PUT'|'PATCH'|'DELETE', headers?: Record<string,string>, body?: unknown }\`
|
|
@@ -478,21 +478,52 @@ if (!result.ok) throw new Error(\`Request failed: \${result.status}\`)
|
|
|
478
478
|
const data = result.data as MyType
|
|
479
479
|
\`\`\`
|
|
480
480
|
|
|
481
|
+
### Secret injection via \`{{settings.xxx}}\` placeholders
|
|
482
|
+
For API keys and tokens stored as \`secret: true\` fields in \`settingsSchema\`, use template placeholders in header values. The proxy resolves them server-side \u2014 **the real secret never enters extension code**.
|
|
483
|
+
|
|
484
|
+
\`\`\`tsx
|
|
485
|
+
const result = await capabilities.data.fetch('https://api.example.com/orders', {
|
|
486
|
+
method: 'GET',
|
|
487
|
+
headers: {
|
|
488
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
489
|
+
'Authorization': 'Bearer {{settings.token}}',
|
|
490
|
+
},
|
|
491
|
+
})
|
|
492
|
+
\`\`\`
|
|
493
|
+
|
|
494
|
+
- Placeholders are only allowed in **header values** (not URLs, header names, or body)
|
|
495
|
+
- For \`required: true\` secret fields, the proxy returns 400 if the value is not configured
|
|
496
|
+
- For optional secret fields, the entire header is omitted if the value is not configured
|
|
497
|
+
- Declare secret fields in your \`manifest.json\` \`settingsSchema\` with \`"secret": true\`
|
|
498
|
+
|
|
481
499
|
## context.read \u2014 Read Host Context
|
|
482
|
-
Read host-provided context (customer ID, email, etc.).
|
|
500
|
+
Read host-provided context (customer ID, email, extension settings, etc.).
|
|
483
501
|
- **Permission required:** \`context:read\`
|
|
484
502
|
- **Usage:** \`capabilities.context.read(): Promise<ContextData>\`
|
|
485
|
-
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, [key: string]: unknown }\`
|
|
486
|
-
- **Convenience
|
|
503
|
+
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, settings?: Record<string, unknown>, [key: string]: unknown }\`
|
|
504
|
+
- **Convenience hooks:**
|
|
505
|
+
- \`useContextData()\` returns \`ContextData & { loading: boolean }\`
|
|
506
|
+
- \`useSettings()\` returns \`Record<string, unknown>\` \u2014 shorthand for \`contextData.settings ?? {}\`
|
|
487
507
|
|
|
488
508
|
\`\`\`tsx
|
|
489
|
-
//
|
|
490
|
-
const { loading, customerId, customerEmail } = useContextData()
|
|
509
|
+
// Read all context (customer + settings)
|
|
510
|
+
const { loading, customerId, customerEmail, settings } = useContextData()
|
|
511
|
+
|
|
512
|
+
// Read only extension settings (convenience)
|
|
513
|
+
const settings = useSettings()
|
|
514
|
+
const apiBaseUrl = settings.baseUrl as string
|
|
491
515
|
|
|
492
516
|
// Alternative: use the capability directly
|
|
493
517
|
const context = await capabilities.context.read()
|
|
494
518
|
\`\`\`
|
|
495
519
|
|
|
520
|
+
### Extension settings in context
|
|
521
|
+
Non-secret settings declared in \`settingsSchema\` are automatically available via \`contextData.settings\`. Values are scoped to the calling extension on the current instance \u2014 an extension never sees other extensions' settings.
|
|
522
|
+
|
|
523
|
+
- **Secret fields are never included** in context \u2014 use \`{{settings.xxx}}\` placeholders in \`data.fetch\` headers instead
|
|
524
|
+
- Settings propagate on **page load** \u2014 changes made in the admin dashboard take effect on the next page reload, not mid-session
|
|
525
|
+
- No new permission needed \u2014 \`context:read\` is the only gate
|
|
526
|
+
|
|
496
527
|
## actions.toast \u2014 Show Toast Notifications
|
|
497
528
|
Display a toast notification in the host UI.
|
|
498
529
|
- **Permission required:** \`actions:toast\`
|
|
@@ -975,9 +1006,16 @@ const viewState = useStore(appStore, (s) => s.viewState)
|
|
|
975
1006
|
\`\`\`
|
|
976
1007
|
|
|
977
1008
|
## useContextData()
|
|
978
|
-
Reads host-provided context. Returns \`{ loading, customerId, customerEmail, ... }\`.
|
|
1009
|
+
Reads host-provided context including extension settings. Returns \`{ loading, customerId, customerEmail, settings, ... }\`.
|
|
1010
|
+
\`\`\`tsx
|
|
1011
|
+
const { loading, customerId, customerEmail, settings } = useContextData()
|
|
1012
|
+
\`\`\`
|
|
1013
|
+
|
|
1014
|
+
## useSettings()
|
|
1015
|
+
Convenience hook for reading extension settings. Returns non-secret settings scoped to this extension on this instance.
|
|
979
1016
|
\`\`\`tsx
|
|
980
|
-
const
|
|
1017
|
+
const settings = useSettings()
|
|
1018
|
+
const apiBaseUrl = settings.baseUrl as string
|
|
981
1019
|
\`\`\`
|
|
982
1020
|
|
|
983
1021
|
## useSurfaceContext()
|
|
@@ -3252,8 +3290,11 @@ const capabilities = useCapabilities()
|
|
|
3252
3290
|
const response = await capabilities.data.fetch('https://api.example.com/endpoint')
|
|
3253
3291
|
`,
|
|
3254
3292
|
"context.read": `
|
|
3255
|
-
|
|
3256
|
-
const
|
|
3293
|
+
// Read host context + extension settings
|
|
3294
|
+
const { customerId, settings } = useContextData()
|
|
3295
|
+
|
|
3296
|
+
// Or use the convenience hook for settings only
|
|
3297
|
+
const settings = useSettings()
|
|
3257
3298
|
`,
|
|
3258
3299
|
"actions.toast": `
|
|
3259
3300
|
const capabilities = useCapabilities()
|