@stackable-labs/mcp-app-extension 0.7.3 → 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 CHANGED
@@ -463,8 +463,8 @@ const result = await capabilities.data.query<Customer>({
463
463
  })
464
464
  \`\`\`
465
465
 
466
- ## data.fetch \u2014 Direct HTTP from Sandbox
467
- Extension makes HTTP requests directly. Domain must be in \`allowedDomains\` in manifest.
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 hook:** \`useContextData()\` returns \`ContextData & { loading: boolean }\`
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
- // Preferred: use the hook
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 { loading, customerId, customerEmail } = useContextData()
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
- const capabilities = useCapabilities()
3258
- const ctx = await capabilities.context.read()
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 Direct HTTP from Sandbox
465
- Extension makes HTTP requests directly. Domain must be in \`allowedDomains\` in manifest.
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 hook:** \`useContextData()\` returns \`ContextData & { loading: boolean }\`
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
- // Preferred: use the hook
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 { loading, customerId, customerEmail } = useContextData()
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
- const capabilities = useCapabilities()
3256
- const ctx = await capabilities.context.read()
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()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackable-labs/mcp-app-extension",
3
- "version": "0.7.3",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mcp-app-extension": "./dist/index.js"