@stackable-labs/mcp-app-extension 0.7.3 → 0.9.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
@@ -43,12 +43,13 @@ export const appStore = createStore<AppState>({
43
43
  {
44
44
  path: "surfaces/Content.tsx",
45
45
  title: "Content Surface with Loading State",
46
- code: `import { ui, useStore, useContextData, Surface } from '@stackable-labs/sdk-extension-react'
46
+ code: `import { ui, useStore, useContextData, useSettings, Surface } from '@stackable-labs/sdk-extension-react'
47
47
  import { appStore } from '../store'
48
48
 
49
49
  export function Content() {
50
50
  const viewState = useStore(appStore, (s) => s.viewState)
51
51
  const { loading } = useContextData()
52
+ const settings = useSettings() // Non-secret settings from settingsSchema
52
53
 
53
54
  if (loading) {
54
55
  return (
@@ -143,19 +144,33 @@ export function createApi(query: QueryFn) {
143
144
  }
144
145
 
145
146
  // \u2500\u2500 data.fetch wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
147
+ //
148
+ // For API keys and secrets, use {{settings.xxx}} placeholders in headers.
149
+ // The proxy resolves them server-side \u2014 the real secret never enters extension code.
150
+ // Declare secret fields in manifest.json settingsSchema with "secret": true.
146
151
 
147
152
  const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
148
153
 
149
154
  export function createFetchApi(fetch: FetchFn) {
150
155
  return {
151
156
  async getItems(): Promise<unknown[]> {
152
- const result = await fetch(\`\${API_BASE_URL}/items\`, { method: 'GET' })
157
+ const result = await fetch(\`\${API_BASE_URL}/items\`, {
158
+ method: 'GET',
159
+ headers: {
160
+ 'X-API-Key': '{{settings.apiKey}}',
161
+ },
162
+ })
153
163
  if (!result.ok) throw new Error(\`getItems failed: \${result.status}\`)
154
164
  return result.data as unknown[]
155
165
  },
156
166
 
157
167
  async getItem(itemId: string): Promise<unknown> {
158
- const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, { method: 'GET' })
168
+ const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
169
+ method: 'GET',
170
+ headers: {
171
+ 'X-API-Key': '{{settings.apiKey}}',
172
+ },
173
+ })
159
174
  if (!result.ok) throw new Error(\`getItem failed: \${result.status}\`)
160
175
  return result.data as unknown
161
176
  },
@@ -182,12 +197,13 @@ export const appStore = createStore<AppState>({
182
197
  {
183
198
  path: "surfaces/Content.tsx",
184
199
  title: "Current Content Surface",
185
- code: `import { ui, useStore, useContextData, Surface } from '@stackable-labs/sdk-extension-react'
200
+ code: `import { ui, useStore, useContextData, useSettings, Surface } from '@stackable-labs/sdk-extension-react'
186
201
  import { appStore } from '../store'
187
202
 
188
203
  export function Content() {
189
204
  const viewState = useStore(appStore, (s) => s.viewState)
190
205
  const { loading } = useContextData()
206
+ const settings = useSettings() // Non-secret settings from settingsSchema
191
207
 
192
208
  if (loading) {
193
209
  return (
@@ -251,19 +267,33 @@ export function createApi(query: QueryFn) {
251
267
  }
252
268
 
253
269
  // \u2500\u2500 data.fetch wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
270
+ //
271
+ // For API keys and secrets, use {{settings.xxx}} placeholders in headers.
272
+ // The proxy resolves them server-side \u2014 the real secret never enters extension code.
273
+ // Declare secret fields in manifest.json settingsSchema with "secret": true.
254
274
 
255
275
  const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
256
276
 
257
277
  export function createFetchApi(fetch: FetchFn) {
258
278
  return {
259
279
  async getItems(): Promise<unknown[]> {
260
- const result = await fetch(\`\${API_BASE_URL}/items\`, { method: 'GET' })
280
+ const result = await fetch(\`\${API_BASE_URL}/items\`, {
281
+ method: 'GET',
282
+ headers: {
283
+ 'X-API-Key': '{{settings.apiKey}}',
284
+ },
285
+ })
261
286
  if (!result.ok) throw new Error(\`getItems failed: \${result.status}\`)
262
287
  return result.data as unknown[]
263
288
  },
264
289
 
265
290
  async getItem(itemId: string): Promise<unknown> {
266
- const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, { method: 'GET' })
291
+ const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
292
+ method: 'GET',
293
+ headers: {
294
+ 'X-API-Key': '{{settings.apiKey}}',
295
+ },
296
+ })
267
297
  if (!result.ok) throw new Error(\`getItem failed: \${result.status}\`)
268
298
  return result.data as unknown
269
299
  },
@@ -463,8 +493,8 @@ const result = await capabilities.data.query<Customer>({
463
493
  })
464
494
  \`\`\`
465
495
 
466
- ## data.fetch \u2014 Direct HTTP from Sandbox
467
- Extension makes HTTP requests directly. Domain must be in \`allowedDomains\` in manifest.
496
+ ## data.fetch \u2014 HTTP Requests to External APIs
497
+ Make HTTP requests to external APIs. Domain must be in \`allowedDomains\` in manifest. Requests are proxied through the Stackable platform server.
468
498
  - **Permission required:** \`data:fetch\`
469
499
  - **Usage:** \`capabilities.data.fetch(url: string, init?: FetchRequestInit): Promise<FetchResponse>\`
470
500
  - **FetchRequestInit:** \`{ method?: 'GET'|'POST'|'PUT'|'PATCH'|'DELETE', headers?: Record<string,string>, body?: unknown }\`
@@ -480,21 +510,52 @@ if (!result.ok) throw new Error(\`Request failed: \${result.status}\`)
480
510
  const data = result.data as MyType
481
511
  \`\`\`
482
512
 
513
+ ### Secret injection via \`{{settings.xxx}}\` placeholders
514
+ 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**.
515
+
516
+ \`\`\`tsx
517
+ const result = await capabilities.data.fetch('https://api.example.com/orders', {
518
+ method: 'GET',
519
+ headers: {
520
+ 'X-API-Key': '{{settings.apiKey}}',
521
+ 'Authorization': 'Bearer {{settings.token}}',
522
+ },
523
+ })
524
+ \`\`\`
525
+
526
+ - Placeholders are only allowed in **header values** (not URLs, header names, or body)
527
+ - For \`required: true\` secret fields, the proxy returns 400 if the value is not configured
528
+ - For optional secret fields, the entire header is omitted if the value is not configured
529
+ - Declare secret fields in your \`manifest.json\` \`settingsSchema\` with \`"secret": true\`
530
+
483
531
  ## context.read \u2014 Read Host Context
484
- Read host-provided context (customer ID, email, etc.).
532
+ Read host-provided context (customer ID, email, extension settings, etc.).
485
533
  - **Permission required:** \`context:read\`
486
534
  - **Usage:** \`capabilities.context.read(): Promise<ContextData>\`
487
- - **ContextData shape:** \`{ customerId?: string, customerEmail?: string, [key: string]: unknown }\`
488
- - **Convenience hook:** \`useContextData()\` returns \`ContextData & { loading: boolean }\`
535
+ - **ContextData shape:** \`{ customerId?: string, customerEmail?: string, settings?: Record<string, unknown>, [key: string]: unknown }\`
536
+ - **Convenience hooks:**
537
+ - \`useContextData()\` returns \`ContextData & { loading: boolean }\`
538
+ - \`useSettings()\` returns \`Record<string, unknown>\` \u2014 shorthand for \`contextData.settings ?? {}\`
489
539
 
490
540
  \`\`\`tsx
491
- // Preferred: use the hook
492
- const { loading, customerId, customerEmail } = useContextData()
541
+ // Read all context (customer + settings)
542
+ const { loading, customerId, customerEmail, settings } = useContextData()
543
+
544
+ // Read only extension settings (convenience)
545
+ const settings = useSettings()
546
+ const apiBaseUrl = settings.baseUrl as string
493
547
 
494
548
  // Alternative: use the capability directly
495
549
  const context = await capabilities.context.read()
496
550
  \`\`\`
497
551
 
552
+ ### Extension settings in context
553
+ 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.
554
+
555
+ - **Secret fields are never included** in context \u2014 use \`{{settings.xxx}}\` placeholders in \`data.fetch\` headers instead
556
+ - Settings propagate on **page load** \u2014 changes made in the admin dashboard take effect on the next page reload, not mid-session
557
+ - No new permission needed \u2014 \`context:read\` is the only gate
558
+
498
559
  ## actions.toast \u2014 Show Toast Notifications
499
560
  Display a toast notification in the host UI.
500
561
  - **Permission required:** \`actions:toast\`
@@ -977,9 +1038,16 @@ const viewState = useStore(appStore, (s) => s.viewState)
977
1038
  \`\`\`
978
1039
 
979
1040
  ## useContextData()
980
- Reads host-provided context. Returns \`{ loading, customerId, customerEmail, ... }\`.
1041
+ Reads host-provided context including extension settings. Returns \`{ loading, customerId, customerEmail, settings, ... }\`.
1042
+ \`\`\`tsx
1043
+ const { loading, customerId, customerEmail, settings } = useContextData()
1044
+ \`\`\`
1045
+
1046
+ ## useSettings()
1047
+ Convenience hook for reading extension settings. Returns non-secret settings scoped to this extension on this instance.
981
1048
  \`\`\`tsx
982
- const { loading, customerId, customerEmail } = useContextData()
1049
+ const settings = useSettings()
1050
+ const apiBaseUrl = settings.baseUrl as string
983
1051
  \`\`\`
984
1052
 
985
1053
  ## useSurfaceContext()
@@ -3254,8 +3322,11 @@ const capabilities = useCapabilities()
3254
3322
  const response = await capabilities.data.fetch('https://api.example.com/endpoint')
3255
3323
  `,
3256
3324
  "context.read": `
3257
- const capabilities = useCapabilities()
3258
- const ctx = await capabilities.context.read()
3325
+ // Read host context + extension settings
3326
+ const { customerId, settings } = useContextData()
3327
+
3328
+ // Or use the convenience hook for settings only
3329
+ const settings = useSettings()
3259
3330
  `,
3260
3331
  "actions.toast": `
3261
3332
  const capabilities = useCapabilities()
@@ -3641,8 +3712,11 @@ var CAPABILITY_SNIPPETS2 = {
3641
3712
  const result = await capabilities.data.query({ path: '/your-endpoint', method: 'GET' })`,
3642
3713
  "data.fetch": `const capabilities = useCapabilities()
3643
3714
  const response = await capabilities.data.fetch('https://api.example.com/endpoint')`,
3644
- "context.read": `const capabilities = useCapabilities()
3645
- const ctx = await capabilities.context.read()`,
3715
+ "context.read": `// Read host context + extension settings
3716
+ const { customerId, settings } = useContextData()
3717
+
3718
+ // Or use the convenience hook for settings only
3719
+ const settings = useSettings()`,
3646
3720
  "actions.toast": `const capabilities = useCapabilities()
3647
3721
  capabilities.actions.toast({ type: 'success', message: 'Done!' })`,
3648
3722
  "actions.invoke": `const capabilities = useCapabilities()
package/dist/server.js CHANGED
@@ -41,12 +41,13 @@ export const appStore = createStore<AppState>({
41
41
  {
42
42
  path: "surfaces/Content.tsx",
43
43
  title: "Content Surface with Loading State",
44
- code: `import { ui, useStore, useContextData, Surface } from '@stackable-labs/sdk-extension-react'
44
+ code: `import { ui, useStore, useContextData, useSettings, Surface } from '@stackable-labs/sdk-extension-react'
45
45
  import { appStore } from '../store'
46
46
 
47
47
  export function Content() {
48
48
  const viewState = useStore(appStore, (s) => s.viewState)
49
49
  const { loading } = useContextData()
50
+ const settings = useSettings() // Non-secret settings from settingsSchema
50
51
 
51
52
  if (loading) {
52
53
  return (
@@ -141,19 +142,33 @@ export function createApi(query: QueryFn) {
141
142
  }
142
143
 
143
144
  // \u2500\u2500 data.fetch wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
145
+ //
146
+ // For API keys and secrets, use {{settings.xxx}} placeholders in headers.
147
+ // The proxy resolves them server-side \u2014 the real secret never enters extension code.
148
+ // Declare secret fields in manifest.json settingsSchema with "secret": true.
144
149
 
145
150
  const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
146
151
 
147
152
  export function createFetchApi(fetch: FetchFn) {
148
153
  return {
149
154
  async getItems(): Promise<unknown[]> {
150
- const result = await fetch(\`\${API_BASE_URL}/items\`, { method: 'GET' })
155
+ const result = await fetch(\`\${API_BASE_URL}/items\`, {
156
+ method: 'GET',
157
+ headers: {
158
+ 'X-API-Key': '{{settings.apiKey}}',
159
+ },
160
+ })
151
161
  if (!result.ok) throw new Error(\`getItems failed: \${result.status}\`)
152
162
  return result.data as unknown[]
153
163
  },
154
164
 
155
165
  async getItem(itemId: string): Promise<unknown> {
156
- const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, { method: 'GET' })
166
+ const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
167
+ method: 'GET',
168
+ headers: {
169
+ 'X-API-Key': '{{settings.apiKey}}',
170
+ },
171
+ })
157
172
  if (!result.ok) throw new Error(\`getItem failed: \${result.status}\`)
158
173
  return result.data as unknown
159
174
  },
@@ -180,12 +195,13 @@ export const appStore = createStore<AppState>({
180
195
  {
181
196
  path: "surfaces/Content.tsx",
182
197
  title: "Current Content Surface",
183
- code: `import { ui, useStore, useContextData, Surface } from '@stackable-labs/sdk-extension-react'
198
+ code: `import { ui, useStore, useContextData, useSettings, Surface } from '@stackable-labs/sdk-extension-react'
184
199
  import { appStore } from '../store'
185
200
 
186
201
  export function Content() {
187
202
  const viewState = useStore(appStore, (s) => s.viewState)
188
203
  const { loading } = useContextData()
204
+ const settings = useSettings() // Non-secret settings from settingsSchema
189
205
 
190
206
  if (loading) {
191
207
  return (
@@ -249,19 +265,33 @@ export function createApi(query: QueryFn) {
249
265
  }
250
266
 
251
267
  // \u2500\u2500 data.fetch wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
268
+ //
269
+ // For API keys and secrets, use {{settings.xxx}} placeholders in headers.
270
+ // The proxy resolves them server-side \u2014 the real secret never enters extension code.
271
+ // Declare secret fields in manifest.json settingsSchema with "secret": true.
252
272
 
253
273
  const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
254
274
 
255
275
  export function createFetchApi(fetch: FetchFn) {
256
276
  return {
257
277
  async getItems(): Promise<unknown[]> {
258
- const result = await fetch(\`\${API_BASE_URL}/items\`, { method: 'GET' })
278
+ const result = await fetch(\`\${API_BASE_URL}/items\`, {
279
+ method: 'GET',
280
+ headers: {
281
+ 'X-API-Key': '{{settings.apiKey}}',
282
+ },
283
+ })
259
284
  if (!result.ok) throw new Error(\`getItems failed: \${result.status}\`)
260
285
  return result.data as unknown[]
261
286
  },
262
287
 
263
288
  async getItem(itemId: string): Promise<unknown> {
264
- const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, { method: 'GET' })
289
+ const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
290
+ method: 'GET',
291
+ headers: {
292
+ 'X-API-Key': '{{settings.apiKey}}',
293
+ },
294
+ })
265
295
  if (!result.ok) throw new Error(\`getItem failed: \${result.status}\`)
266
296
  return result.data as unknown
267
297
  },
@@ -461,8 +491,8 @@ const result = await capabilities.data.query<Customer>({
461
491
  })
462
492
  \`\`\`
463
493
 
464
- ## data.fetch \u2014 Direct HTTP from Sandbox
465
- Extension makes HTTP requests directly. Domain must be in \`allowedDomains\` in manifest.
494
+ ## data.fetch \u2014 HTTP Requests to External APIs
495
+ Make HTTP requests to external APIs. Domain must be in \`allowedDomains\` in manifest. Requests are proxied through the Stackable platform server.
466
496
  - **Permission required:** \`data:fetch\`
467
497
  - **Usage:** \`capabilities.data.fetch(url: string, init?: FetchRequestInit): Promise<FetchResponse>\`
468
498
  - **FetchRequestInit:** \`{ method?: 'GET'|'POST'|'PUT'|'PATCH'|'DELETE', headers?: Record<string,string>, body?: unknown }\`
@@ -478,21 +508,52 @@ if (!result.ok) throw new Error(\`Request failed: \${result.status}\`)
478
508
  const data = result.data as MyType
479
509
  \`\`\`
480
510
 
511
+ ### Secret injection via \`{{settings.xxx}}\` placeholders
512
+ 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**.
513
+
514
+ \`\`\`tsx
515
+ const result = await capabilities.data.fetch('https://api.example.com/orders', {
516
+ method: 'GET',
517
+ headers: {
518
+ 'X-API-Key': '{{settings.apiKey}}',
519
+ 'Authorization': 'Bearer {{settings.token}}',
520
+ },
521
+ })
522
+ \`\`\`
523
+
524
+ - Placeholders are only allowed in **header values** (not URLs, header names, or body)
525
+ - For \`required: true\` secret fields, the proxy returns 400 if the value is not configured
526
+ - For optional secret fields, the entire header is omitted if the value is not configured
527
+ - Declare secret fields in your \`manifest.json\` \`settingsSchema\` with \`"secret": true\`
528
+
481
529
  ## context.read \u2014 Read Host Context
482
- Read host-provided context (customer ID, email, etc.).
530
+ Read host-provided context (customer ID, email, extension settings, etc.).
483
531
  - **Permission required:** \`context:read\`
484
532
  - **Usage:** \`capabilities.context.read(): Promise<ContextData>\`
485
- - **ContextData shape:** \`{ customerId?: string, customerEmail?: string, [key: string]: unknown }\`
486
- - **Convenience hook:** \`useContextData()\` returns \`ContextData & { loading: boolean }\`
533
+ - **ContextData shape:** \`{ customerId?: string, customerEmail?: string, settings?: Record<string, unknown>, [key: string]: unknown }\`
534
+ - **Convenience hooks:**
535
+ - \`useContextData()\` returns \`ContextData & { loading: boolean }\`
536
+ - \`useSettings()\` returns \`Record<string, unknown>\` \u2014 shorthand for \`contextData.settings ?? {}\`
487
537
 
488
538
  \`\`\`tsx
489
- // Preferred: use the hook
490
- const { loading, customerId, customerEmail } = useContextData()
539
+ // Read all context (customer + settings)
540
+ const { loading, customerId, customerEmail, settings } = useContextData()
541
+
542
+ // Read only extension settings (convenience)
543
+ const settings = useSettings()
544
+ const apiBaseUrl = settings.baseUrl as string
491
545
 
492
546
  // Alternative: use the capability directly
493
547
  const context = await capabilities.context.read()
494
548
  \`\`\`
495
549
 
550
+ ### Extension settings in context
551
+ 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.
552
+
553
+ - **Secret fields are never included** in context \u2014 use \`{{settings.xxx}}\` placeholders in \`data.fetch\` headers instead
554
+ - Settings propagate on **page load** \u2014 changes made in the admin dashboard take effect on the next page reload, not mid-session
555
+ - No new permission needed \u2014 \`context:read\` is the only gate
556
+
496
557
  ## actions.toast \u2014 Show Toast Notifications
497
558
  Display a toast notification in the host UI.
498
559
  - **Permission required:** \`actions:toast\`
@@ -975,9 +1036,16 @@ const viewState = useStore(appStore, (s) => s.viewState)
975
1036
  \`\`\`
976
1037
 
977
1038
  ## useContextData()
978
- Reads host-provided context. Returns \`{ loading, customerId, customerEmail, ... }\`.
1039
+ Reads host-provided context including extension settings. Returns \`{ loading, customerId, customerEmail, settings, ... }\`.
1040
+ \`\`\`tsx
1041
+ const { loading, customerId, customerEmail, settings } = useContextData()
1042
+ \`\`\`
1043
+
1044
+ ## useSettings()
1045
+ Convenience hook for reading extension settings. Returns non-secret settings scoped to this extension on this instance.
979
1046
  \`\`\`tsx
980
- const { loading, customerId, customerEmail } = useContextData()
1047
+ const settings = useSettings()
1048
+ const apiBaseUrl = settings.baseUrl as string
981
1049
  \`\`\`
982
1050
 
983
1051
  ## useSurfaceContext()
@@ -3252,8 +3320,11 @@ const capabilities = useCapabilities()
3252
3320
  const response = await capabilities.data.fetch('https://api.example.com/endpoint')
3253
3321
  `,
3254
3322
  "context.read": `
3255
- const capabilities = useCapabilities()
3256
- const ctx = await capabilities.context.read()
3323
+ // Read host context + extension settings
3324
+ const { customerId, settings } = useContextData()
3325
+
3326
+ // Or use the convenience hook for settings only
3327
+ const settings = useSettings()
3257
3328
  `,
3258
3329
  "actions.toast": `
3259
3330
  const capabilities = useCapabilities()
@@ -3639,8 +3710,11 @@ var CAPABILITY_SNIPPETS2 = {
3639
3710
  const result = await capabilities.data.query({ path: '/your-endpoint', method: 'GET' })`,
3640
3711
  "data.fetch": `const capabilities = useCapabilities()
3641
3712
  const response = await capabilities.data.fetch('https://api.example.com/endpoint')`,
3642
- "context.read": `const capabilities = useCapabilities()
3643
- const ctx = await capabilities.context.read()`,
3713
+ "context.read": `// Read host context + extension settings
3714
+ const { customerId, settings } = useContextData()
3715
+
3716
+ // Or use the convenience hook for settings only
3717
+ const settings = useSettings()`,
3644
3718
  "actions.toast": `const capabilities = useCapabilities()
3645
3719
  capabilities.actions.toast({ type: 'success', message: 'Done!' })`,
3646
3720
  "actions.invoke": `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.9.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mcp-app-extension": "./dist/index.js"