@webiny/mcp 6.2.0 → 6.3.0-beta.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webiny/mcp",
3
- "version": "6.2.0",
3
+ "version": "6.3.0-beta.0",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -25,13 +25,13 @@
25
25
  "devDependencies": {
26
26
  "@types/lodash": "4.17.24",
27
27
  "@types/ncp": "2.0.8",
28
- "@webiny/build-tools": "6.2.0",
28
+ "@webiny/build-tools": "6.3.0-beta.0",
29
29
  "execa": "5.1.1",
30
30
  "tsx": "4.21.0",
31
- "typescript": "5.9.3"
31
+ "typescript": "6.0.3"
32
32
  },
33
33
  "scripts": {
34
34
  "prepublishOnly": "bash ./prepublishOnly.sh"
35
35
  },
36
- "gitHead": "3d3148358b6febbc857371930871743bec3b3939"
36
+ "gitHead": "94c21e58aebc9855bf1ae972423281faa0f5c135"
37
37
  }
@@ -317,27 +317,27 @@ import { makeAutoObservable, runInAction } from "mobx";
317
317
  import { WcpService as ServiceAbstraction, WcpGateway } from "./abstractions.js";
318
318
 
319
319
  class WcpServiceImpl implements ServiceAbstraction.Interface {
320
- private project: ILicense | null = null;
320
+ private project: ILicense | null = null;
321
321
 
322
- constructor(private gateway: WcpGateway.Interface) {
323
- makeAutoObservable(this);
324
- }
322
+ constructor(private gateway: WcpGateway.Interface) {
323
+ makeAutoObservable(this);
324
+ }
325
325
 
326
- getProject(): ILicense {
327
- return this.project;
328
- }
326
+ getProject(): ILicense {
327
+ return this.project;
328
+ }
329
329
 
330
- async loadProject(): Promise<void> {
331
- const data = await this.gateway.fetchProject();
332
- runInAction(() => {
333
- this.project = data;
334
- });
335
- }
330
+ async loadProject(): Promise<void> {
331
+ const data = await this.gateway.fetchProject();
332
+ runInAction(() => {
333
+ this.project = data;
334
+ });
335
+ }
336
336
  }
337
337
 
338
338
  export const WcpService = ServiceAbstraction.createImplementation({
339
- implementation: WcpServiceImpl,
340
- dependencies: [WcpGateway]
339
+ implementation: WcpServiceImpl,
340
+ dependencies: [WcpGateway]
341
341
  });
342
342
  ```
343
343
 
@@ -352,37 +352,37 @@ Repositories own domain data and cache. They use MobX for reactivity:
352
352
  ```ts
353
353
  import { makeAutoObservable, runInAction } from "mobx";
354
354
  import {
355
- NextjsConfigRepository as RepositoryAbstraction,
356
- NextjsConfigGateway,
357
- NextjsConfig
355
+ NextjsConfigRepository as RepositoryAbstraction,
356
+ NextjsConfigGateway,
357
+ NextjsConfig
358
358
  } from "./abstractions.js";
359
359
 
360
360
  class NextjsConfigRepositoryImpl implements RepositoryAbstraction.Interface {
361
- private config: NextjsConfig | undefined = undefined;
362
-
363
- constructor(private gateway: NextjsConfigGateway.Interface) {
364
- makeAutoObservable(this);
365
- }
361
+ private config: NextjsConfig | undefined = undefined;
366
362
 
367
- getConfig(): NextjsConfig | undefined {
368
- return this.config;
369
- }
363
+ constructor(private gateway: NextjsConfigGateway.Interface) {
364
+ makeAutoObservable(this);
365
+ }
370
366
 
371
- async loadConfig(): Promise<void> {
372
- if (this.config) {
373
- return; // Already loaded — cache hit
374
- }
367
+ getConfig(): NextjsConfig | undefined {
368
+ return this.config;
369
+ }
375
370
 
376
- const config = await this.gateway.getConfig();
377
- runInAction(() => {
378
- this.config = config;
379
- });
371
+ async loadConfig(): Promise<void> {
372
+ if (this.config) {
373
+ return; // Already loaded — cache hit
380
374
  }
375
+
376
+ const config = await this.gateway.getConfig();
377
+ runInAction(() => {
378
+ this.config = config;
379
+ });
380
+ }
381
381
  }
382
382
 
383
383
  export const NextjsConfigRepository = RepositoryAbstraction.createImplementation({
384
- implementation: NextjsConfigRepositoryImpl,
385
- dependencies: [NextjsConfigGateway]
384
+ implementation: NextjsConfigRepositoryImpl,
385
+ dependencies: [NextjsConfigGateway]
386
386
  });
387
387
  ```
388
388
 
@@ -395,48 +395,53 @@ import { NextjsConfigGateway as GatewayAbstraction } from "./abstractions.js";
395
395
  import { GraphQLClient } from "@webiny/app/features/graphqlClient";
396
396
 
397
397
  const GET_NEXTJS_CONFIG = /* GraphQL */ `
398
- query GetNextjsConfig {
399
- websiteBuilder {
400
- getNextjsConfig {
401
- data
402
- error { code message data }
403
- }
398
+ query GetNextjsConfig {
399
+ websiteBuilder {
400
+ getNextjsConfig {
401
+ data
402
+ error {
403
+ code
404
+ message
405
+ data
404
406
  }
407
+ }
405
408
  }
409
+ }
406
410
  `;
407
411
 
408
412
  type GetNextjsConfigResponse = {
409
- websiteBuilder: {
410
- getNextjsConfig:
411
- | { data: string; error: null }
412
- | { data: null; error: { code: string; message: string; data: any } };
413
- };
413
+ websiteBuilder: {
414
+ getNextjsConfig:
415
+ | { data: string; error: null }
416
+ | { data: null; error: { code: string; message: string; data: any } };
417
+ };
414
418
  };
415
419
 
416
420
  class NextjsGraphQLGateway implements GatewayAbstraction.Interface {
417
- constructor(private client: GraphQLClient.Interface) {}
418
-
419
- async getConfig(): Promise<string> {
420
- const response = await this.client.execute<GetNextjsConfigResponse>({
421
- query: GET_NEXTJS_CONFIG
422
- });
421
+ constructor(private client: GraphQLClient.Interface) {}
423
422
 
424
- const envelope = response.websiteBuilder.getNextjsConfig;
425
- if (envelope.error) {
426
- throw new Error(envelope.error.message);
427
- }
423
+ async getConfig(): Promise<string> {
424
+ const response = await this.client.execute<GetNextjsConfigResponse>({
425
+ query: GET_NEXTJS_CONFIG
426
+ });
428
427
 
429
- return envelope.data;
428
+ const envelope = response.websiteBuilder.getNextjsConfig;
429
+ if (envelope.error) {
430
+ throw new Error(envelope.error.message);
430
431
  }
432
+
433
+ return envelope.data;
434
+ }
431
435
  }
432
436
 
433
437
  export const NextjsConfigGateway = GatewayAbstraction.createImplementation({
434
- implementation: NextjsGraphQLGateway,
435
- dependencies: [GraphQLClient]
438
+ implementation: NextjsGraphQLGateway,
439
+ dependencies: [GraphQLClient]
436
440
  });
437
441
  ```
438
442
 
439
443
  **Key points:**
444
+
440
445
  - Define the GraphQL query as a string constant with `/* GraphQL */` comment for syntax highlighting
441
446
  - Type the response shape explicitly
442
447
  - Handle the `data`/`error` envelope pattern
@@ -450,12 +455,12 @@ When grouping related features, create a composite with no `resolve`:
450
455
  import { createFeature } from "webiny/admin";
451
456
 
452
457
  export const FoldersFeature = createFeature({
453
- name: "Folders",
454
- register(container) {
455
- CreateFolderFeature.register(container);
456
- UpdateFolderFeature.register(container);
457
- DeleteFolderFeature.register(container);
458
- }
458
+ name: "Folders",
459
+ register(container) {
460
+ CreateFolderFeature.register(container);
461
+ UpdateFolderFeature.register(container);
462
+ DeleteFolderFeature.register(container);
463
+ }
459
464
  });
460
465
  ```
461
466
 
@@ -465,17 +470,14 @@ Decorators add cross-cutting concerns without modifying the core implementation:
465
470
 
466
471
  ```ts
467
472
  class ListFoldersUseCaseWithLoading implements UseCaseAbstraction.Interface {
468
- constructor(
469
- private loadingRepository: FoldersLoadingRepository.Interface,
470
- private decoratee: UseCaseAbstraction.Interface // decoratee is LAST
471
- ) {}
472
-
473
- async execute() {
474
- await this.loadingRepository.runCallBack(
475
- this.decoratee.execute(),
476
- LoadingActionsEnum.list
477
- );
478
- }
473
+ constructor(
474
+ private loadingRepository: FoldersLoadingRepository.Interface,
475
+ private decoratee: UseCaseAbstraction.Interface // decoratee is LAST
476
+ ) {}
477
+
478
+ async execute() {
479
+ await this.loadingRepository.runCallBack(this.decoratee.execute(), LoadingActionsEnum.list);
480
+ }
479
481
  }
480
482
  ```
481
483
 
@@ -483,14 +485,15 @@ Register with `container.registerDecorator()`:
483
485
 
484
486
  ```ts
485
487
  export const MyExtensionFeature = createFeature({
486
- name: "MyExtension",
487
- register(container) {
488
- container.registerDecorator(MyPresenterDecorator);
489
- }
488
+ name: "MyExtension",
489
+ register(container) {
490
+ container.registerDecorator(MyPresenterDecorator);
491
+ }
490
492
  });
491
493
  ```
492
494
 
493
495
  **Rules:**
496
+
494
497
  - Implements the same interface as the decorated abstraction
495
498
  - Constructor: extra dependencies first, `decoratee` **last**
496
499
  - The `dependencies` array does NOT include the decoratee
@@ -90,7 +90,9 @@ class MyHandler implements SomeEventHandler.Interface {
90
90
  constructor(private someUseCase: SomeUseCase.Interface) {}
91
91
 
92
92
  async handle(event: SomeEventHandler.Event) {
93
- const result = await this.someUseCase.execute({ /* ... */ });
93
+ const result = await this.someUseCase.execute({
94
+ /* ... */
95
+ });
94
96
  }
95
97
  }
96
98
 
@@ -120,30 +122,30 @@ import type { EntityBeforeDisableEvent, EntityAfterDisableEvent } from "./events
120
122
 
121
123
  // Event Payload Types
122
124
  export interface EntityBeforeDisablePayload {
123
- entity: Entity;
125
+ entity: Entity;
124
126
  }
125
127
 
126
128
  export interface EntityAfterDisablePayload {
127
- entity: Entity;
129
+ entity: Entity;
128
130
  }
129
131
 
130
132
  // Handler Abstractions — one per event
131
133
  export const EntityBeforeDisableEventHandler = createAbstraction<
132
- IEventHandler<EntityBeforeDisableEvent>
134
+ IEventHandler<EntityBeforeDisableEvent>
133
135
  >("MyPackage/EntityBeforeDisableEventHandler");
134
136
 
135
137
  export namespace EntityBeforeDisableEventHandler {
136
- export type Interface = IEventHandler<EntityBeforeDisableEvent>;
137
- export type Event = EntityBeforeDisableEvent;
138
+ export type Interface = IEventHandler<EntityBeforeDisableEvent>;
139
+ export type Event = EntityBeforeDisableEvent;
138
140
  }
139
141
 
140
142
  export const EntityAfterDisableEventHandler = createAbstraction<
141
- IEventHandler<EntityAfterDisableEvent>
143
+ IEventHandler<EntityAfterDisableEvent>
142
144
  >("MyPackage/EntityAfterDisableEventHandler");
143
145
 
144
146
  export namespace EntityAfterDisableEventHandler {
145
- export type Interface = IEventHandler<EntityAfterDisableEvent>;
146
- export type Event = EntityAfterDisableEvent;
147
+ export type Interface = IEventHandler<EntityAfterDisableEvent>;
148
+ export type Event = EntityAfterDisableEvent;
147
149
  }
148
150
  ```
149
151
 
@@ -154,29 +156,23 @@ Event classes import payload types and handler abstractions from `abstractions.t
154
156
  ```ts
155
157
  // features/disableEntity/events.ts
156
158
  import { DomainEvent } from "@webiny/api-core/features/EventPublisher";
157
- import {
158
- EntityBeforeDisableEventHandler,
159
- EntityAfterDisableEventHandler
160
- } from "./abstractions.js";
161
- import type {
162
- EntityBeforeDisablePayload,
163
- EntityAfterDisablePayload
164
- } from "./abstractions.js";
159
+ import { EntityBeforeDisableEventHandler, EntityAfterDisableEventHandler } from "./abstractions.js";
160
+ import type { EntityBeforeDisablePayload, EntityAfterDisablePayload } from "./abstractions.js";
165
161
 
166
162
  export class EntityBeforeDisableEvent extends DomainEvent<EntityBeforeDisablePayload> {
167
- eventType = "entity.beforeDisable" as const;
163
+ eventType = "entity.beforeDisable" as const;
168
164
 
169
- getHandlerAbstraction() {
170
- return EntityBeforeDisableEventHandler;
171
- }
165
+ getHandlerAbstraction() {
166
+ return EntityBeforeDisableEventHandler;
167
+ }
172
168
  }
173
169
 
174
170
  export class EntityAfterDisableEvent extends DomainEvent<EntityAfterDisablePayload> {
175
- eventType = "entity.afterDisable" as const;
171
+ eventType = "entity.afterDisable" as const;
176
172
 
177
- getHandlerAbstraction() {
178
- return EntityAfterDisableEventHandler;
179
- }
173
+ getHandlerAbstraction() {
174
+ return EntityAfterDisableEventHandler;
175
+ }
180
176
  }
181
177
  ```
182
178
 
@@ -188,35 +184,35 @@ import { EventPublisher } from "@webiny/api-core/features/EventPublisher";
188
184
  import { EntityBeforeDisableEvent, EntityAfterDisableEvent } from "./events.js";
189
185
 
190
186
  class DisableEntityUseCase implements UseCaseAbstraction.Interface {
191
- constructor(
192
- private eventPublisher: EventPublisher.Interface,
193
- private getEntityById: GetEntityByIdUseCase.Interface,
194
- private updateEntity: UpdateEntityUseCase.Interface
195
- ) {}
187
+ constructor(
188
+ private eventPublisher: EventPublisher.Interface,
189
+ private getEntityById: GetEntityByIdUseCase.Interface,
190
+ private updateEntity: UpdateEntityUseCase.Interface
191
+ ) {}
196
192
 
197
- async execute(entityId: string): Promise<Result<void, UseCaseAbstraction.Error>> {
198
- const getResult = await this.getEntityById.execute(entityId);
199
- if (getResult.isFail()) return Result.fail(getResult.error);
193
+ async execute(entityId: string): Promise<Result<void, UseCaseAbstraction.Error>> {
194
+ const getResult = await this.getEntityById.execute(entityId);
195
+ if (getResult.isFail()) return Result.fail(getResult.error);
200
196
 
201
- const entity = getResult.value;
197
+ const entity = getResult.value;
202
198
 
203
- // Publish BEFORE event (can be intercepted to reject)
204
- await this.eventPublisher.publish(new EntityBeforeDisableEvent({ entity }));
199
+ // Publish BEFORE event (can be intercepted to reject)
200
+ await this.eventPublisher.publish(new EntityBeforeDisableEvent({ entity }));
205
201
 
206
- // Perform the operation
207
- const updateResult = await this.updateEntity.execute(entityId, { status: "disabled" });
208
- if (updateResult.isFail()) return Result.fail(updateResult.error);
202
+ // Perform the operation
203
+ const updateResult = await this.updateEntity.execute(entityId, { status: "disabled" });
204
+ if (updateResult.isFail()) return Result.fail(updateResult.error);
209
205
 
210
- // Publish AFTER event (for side effects)
211
- await this.eventPublisher.publish(new EntityAfterDisableEvent({ entity: updateResult.value }));
206
+ // Publish AFTER event (for side effects)
207
+ await this.eventPublisher.publish(new EntityAfterDisableEvent({ entity: updateResult.value }));
212
208
 
213
- return Result.ok();
214
- }
209
+ return Result.ok();
210
+ }
215
211
  }
216
212
 
217
213
  export default UseCaseAbstraction.createImplementation({
218
- implementation: DisableEntityUseCase,
219
- dependencies: [EventPublisher, GetEntityByIdUseCase, UpdateEntityUseCase]
214
+ implementation: DisableEntityUseCase,
215
+ dependencies: [EventPublisher, GetEntityByIdUseCase, UpdateEntityUseCase]
220
216
  });
221
217
  ```
222
218
 
@@ -233,23 +229,23 @@ import { CleanupService } from "../cleanupService/abstractions.js";
233
229
  import { MY_MODEL_ID } from "~/shared/constants.js";
234
230
 
235
231
  class CleanupOnEntryDeleteHandler implements EntryAfterDeleteEventHandler.Interface {
236
- constructor(private cleanupService: CleanupService.Interface) {}
232
+ constructor(private cleanupService: CleanupService.Interface) {}
237
233
 
238
- async handle(event: EntryAfterDeleteEventHandler.Event): Promise<void> {
239
- const { entry, model } = event.payload;
234
+ async handle(event: EntryAfterDeleteEventHandler.Event): Promise<void> {
235
+ const { entry, model } = event.payload;
240
236
 
241
- // ALWAYS filter by model — handler fires for ALL models
242
- if (model.modelId !== MY_MODEL_ID) return;
237
+ // ALWAYS filter by model — handler fires for ALL models
238
+ if (model.modelId !== MY_MODEL_ID) return;
243
239
 
244
- if (!event.payload.permanent) return;
240
+ if (!event.payload.permanent) return;
245
241
 
246
- await this.cleanupService.cleanup(entry.entryId);
247
- }
242
+ await this.cleanupService.cleanup(entry.entryId);
243
+ }
248
244
  }
249
245
 
250
246
  export default EntryAfterDeleteEventHandler.createImplementation({
251
- implementation: CleanupOnEntryDeleteHandler,
252
- dependencies: [CleanupService]
247
+ implementation: CleanupOnEntryDeleteHandler,
248
+ dependencies: [CleanupService]
253
249
  });
254
250
  ```
255
251
 
@@ -257,11 +253,11 @@ export default EntryAfterDeleteEventHandler.createImplementation({
257
253
 
258
254
  ## Event Naming Conventions
259
255
 
260
- | Artifact | Pattern | Example |
261
- | ------------------ | ---------------------------------------------- | ---------------------------------- |
262
- | `eventType` | `"entity.beforeAction"` / `"entity.afterAction"` | `"tenant.beforeDisable"` |
263
- | Handler abstraction | `{Entity}{Before\|After}{Action}EventHandler` | `TenantBeforeDisableEventHandler` |
264
- | Event class | `{Entity}{Before\|After}{Action}Event` | `TenantBeforeDisableEvent` |
256
+ | Artifact | Pattern | Example |
257
+ | ------------------- | ------------------------------------------------ | --------------------------------- |
258
+ | `eventType` | `"entity.beforeAction"` / `"entity.afterAction"` | `"tenant.beforeDisable"` |
259
+ | Handler abstraction | `{Entity}{Before\|After}{Action}EventHandler` | `TenantBeforeDisableEventHandler` |
260
+ | Event class | `{Entity}{Before\|After}{Action}Event` | `TenantBeforeDisableEvent` |
265
261
 
266
262
  ## Registration
267
263
 
@@ -106,7 +106,6 @@ reply.code(201).header("X-Custom", "value").send({ created: true });
106
106
  The `<Api.Route>` extension does two things at build/deploy time:
107
107
 
108
108
  1. **Build time** — injects two entries into `apps/api/graphql/src/extensions.ts`:
109
-
110
109
  - A `createContextPlugin` that registers your handler in the DI container
111
110
  - A `createRoute` that registers the Fastify route with the hardcoded `path` and `method`
112
111