@01.software/cli 0.9.0 → 0.10.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.
@@ -91,6 +91,285 @@ function parseJsonWhere(where) {
91
91
  }
92
92
  }
93
93
 
94
+ // ../../packages/contracts/src/tenant/index.ts
95
+ import { z } from "zod";
96
+ var tenantFieldConfigStateSchema = z.object({
97
+ hiddenFields: z.array(z.string()),
98
+ isHidden: z.boolean()
99
+ }).strict();
100
+ var tenantContextQuerySchema = z.object({
101
+ counts: z.literal("true").optional()
102
+ }).strict();
103
+ var tenantContextToolInputSchema = z.object({
104
+ includeCounts: z.boolean().optional().default(false).describe(
105
+ "Include per-collection document counts and config status (bypasses cache, slower)"
106
+ )
107
+ }).strict();
108
+ var tenantContextResponseSchema = z.object({
109
+ tenant: z.object({
110
+ id: z.string(),
111
+ name: z.string(),
112
+ plan: z.string(),
113
+ planSource: z.string().optional(),
114
+ authoritative: z.boolean().optional(),
115
+ capabilityVersion: z.string().optional()
116
+ }).strict(),
117
+ features: z.array(z.string()),
118
+ collections: z.object({
119
+ active: z.array(z.string()),
120
+ inactive: z.array(z.string())
121
+ }).strict(),
122
+ fieldConfigs: z.record(z.string(), tenantFieldConfigStateSchema),
123
+ counts: z.record(z.string(), z.number()).optional(),
124
+ config: z.object({
125
+ webhookConfigured: z.boolean()
126
+ }).strict().optional()
127
+ }).strict();
128
+ var COLLECTION_SCHEMA_CONTRACT_VERSION = 1;
129
+ var collectionSchemaEndpointParamsSchema = z.object({
130
+ collectionSlug: z.string().min(1, "collectionSlug is required")
131
+ }).strict();
132
+ function createCollectionSchemaToolInputSchema(collections) {
133
+ return z.object({
134
+ collection: z.enum(collections).describe("Collection name (required)")
135
+ }).strict();
136
+ }
137
+ var collectionFieldOptionSchema = z.object({
138
+ label: z.string(),
139
+ value: z.string()
140
+ }).strict();
141
+ var collectionFieldSchema = z.lazy(
142
+ () => z.object({
143
+ name: z.string(),
144
+ path: z.string(),
145
+ type: z.string(),
146
+ required: z.literal(true).optional(),
147
+ unique: z.literal(true).optional(),
148
+ hasMany: z.literal(true).optional(),
149
+ relationTo: z.union([z.string(), z.array(z.string())]).optional(),
150
+ options: z.array(collectionFieldOptionSchema).optional(),
151
+ hidden: z.literal(true).optional(),
152
+ systemManaged: z.literal(true).optional(),
153
+ writable: z.boolean().optional(),
154
+ fields: z.array(collectionFieldSchema).optional()
155
+ }).strict()
156
+ );
157
+ var collectionSchemaResponseSchema = z.object({
158
+ contractVersion: z.literal(COLLECTION_SCHEMA_CONTRACT_VERSION),
159
+ mode: z.literal("effective"),
160
+ collection: z.object({
161
+ slug: z.string(),
162
+ timestamps: z.boolean(),
163
+ alwaysActive: z.boolean(),
164
+ feature: z.string().nullable(),
165
+ systemFields: z.array(z.string()),
166
+ visibility: z.object({
167
+ collectionHidden: z.boolean(),
168
+ hiddenFields: z.array(z.string())
169
+ }).strict(),
170
+ fields: z.array(collectionFieldSchema)
171
+ }).strict()
172
+ }).strict();
173
+
174
+ // ../../packages/contracts/src/ecommerce/index.ts
175
+ import { z as z2 } from "zod";
176
+ var transactionStatusSchema = z2.enum([
177
+ "pending",
178
+ "paid",
179
+ "failed",
180
+ "canceled"
181
+ ]);
182
+ var updateTransactionSchema = z2.object({
183
+ pgPaymentId: z2.string().min(1, "pgPaymentId is required").describe("PG payment ID (required)"),
184
+ status: transactionStatusSchema.describe(
185
+ "New transaction status (required)"
186
+ ),
187
+ paymentMethod: z2.string().optional().describe("Payment method (optional)"),
188
+ receiptUrl: z2.string().optional().describe("Receipt URL (optional)"),
189
+ paymentKey: z2.string().min(1).optional().describe("Provider payment key for verified paid confirmation"),
190
+ amount: z2.number().int().positive().optional().describe("Provider-confirmed amount for verified paid confirmation")
191
+ }).strict();
192
+ var UpdateTransactionSchema = updateTransactionSchema;
193
+ var returnReasonSchema = z2.enum([
194
+ "change_of_mind",
195
+ "defective",
196
+ "wrong_delivery",
197
+ "damaged",
198
+ "other"
199
+ ]);
200
+ var restockActionSchema = z2.enum(["return_to_stock", "discard"]);
201
+ var returnWithRefundItemSchema = z2.object({
202
+ orderItem: z2.union([z2.string(), z2.number()]).transform(String),
203
+ quantity: z2.number().int().positive("quantity must be a positive integer"),
204
+ restockAction: restockActionSchema.default("return_to_stock")
205
+ }).strict();
206
+ var returnWithRefundSchema = z2.object({
207
+ orderNumber: z2.string().min(1, "orderNumber is required").describe("Order number (required)"),
208
+ reason: returnReasonSchema.optional().describe("Return reason (optional)"),
209
+ reasonDetail: z2.string().optional().describe("Detailed reason text (optional)"),
210
+ returnItems: z2.array(returnWithRefundItemSchema).min(1, "At least one return item is required").max(100, "Too many return items").describe("Array of products to return (required)"),
211
+ refundAmount: z2.number().min(0, "refundAmount must be non-negative").describe("Refund amount (required, min 0)"),
212
+ pgPaymentId: z2.string().min(1, "pgPaymentId is required").describe("PG payment ID for refund (required)"),
213
+ paymentKey: z2.string().min(1).optional().describe("Provider payment key for verified refund"),
214
+ refundReceiptUrl: z2.string().optional().describe("Refund receipt URL (optional)")
215
+ }).strict();
216
+ var ReturnWithRefundSchema = returnWithRefundSchema;
217
+
218
+ // ../../packages/contracts/src/mcp/index.ts
219
+ var MCP_TOOL_CONTRACT = {
220
+ "query-collection": {
221
+ consoleRole: "tenant-viewer",
222
+ oauthScope: "mcp:read",
223
+ readOnly: true
224
+ },
225
+ "get-collection-by-id": {
226
+ consoleRole: "tenant-viewer",
227
+ oauthScope: "mcp:read",
228
+ readOnly: true
229
+ },
230
+ "get-order": {
231
+ consoleRole: "tenant-viewer",
232
+ oauthScope: "mcp:read",
233
+ readOnly: true
234
+ },
235
+ "stock-check": {
236
+ consoleRole: "tenant-viewer",
237
+ oauthScope: "mcp:read",
238
+ readOnly: true
239
+ },
240
+ "validate-discount": {
241
+ consoleRole: "tenant-viewer",
242
+ oauthScope: "mcp:read",
243
+ readOnly: true
244
+ },
245
+ "calculate-shipping": {
246
+ consoleRole: "tenant-viewer",
247
+ oauthScope: "mcp:read",
248
+ readOnly: true
249
+ },
250
+ "get-collection-schema": {
251
+ consoleRole: "tenant-viewer",
252
+ oauthScope: "mcp:read",
253
+ readOnly: true
254
+ },
255
+ "list-configurable-fields": {
256
+ consoleRole: "tenant-viewer",
257
+ oauthScope: "mcp:read",
258
+ readOnly: true
259
+ },
260
+ "get-tenant-context": {
261
+ consoleRole: "tenant-viewer",
262
+ oauthScope: "mcp:read",
263
+ readOnly: true
264
+ },
265
+ "add-cart-item": {
266
+ consoleRole: "tenant-editor",
267
+ oauthScope: "mcp:write",
268
+ readOnly: false
269
+ },
270
+ "update-cart-item": {
271
+ consoleRole: "tenant-editor",
272
+ oauthScope: "mcp:write",
273
+ readOnly: false
274
+ },
275
+ "remove-cart-item": {
276
+ consoleRole: "tenant-editor",
277
+ oauthScope: "mcp:write",
278
+ readOnly: false
279
+ },
280
+ "clear-cart": {
281
+ consoleRole: "tenant-editor",
282
+ oauthScope: "mcp:write",
283
+ readOnly: false
284
+ },
285
+ "apply-discount": {
286
+ consoleRole: "tenant-editor",
287
+ oauthScope: "mcp:write",
288
+ readOnly: false
289
+ },
290
+ "remove-discount": {
291
+ consoleRole: "tenant-editor",
292
+ oauthScope: "mcp:write",
293
+ readOnly: false
294
+ },
295
+ checkout: {
296
+ consoleRole: "tenant-admin",
297
+ oauthScope: "mcp:write",
298
+ readOnly: false
299
+ },
300
+ "create-order": {
301
+ consoleRole: "tenant-admin",
302
+ oauthScope: "mcp:write",
303
+ readOnly: false
304
+ },
305
+ "update-order": {
306
+ consoleRole: "tenant-admin",
307
+ oauthScope: "mcp:write",
308
+ readOnly: false
309
+ },
310
+ "create-fulfillment": {
311
+ consoleRole: "tenant-admin",
312
+ oauthScope: "mcp:write",
313
+ readOnly: false
314
+ },
315
+ "update-fulfillment": {
316
+ consoleRole: "tenant-admin",
317
+ oauthScope: "mcp:write",
318
+ readOnly: false
319
+ },
320
+ "create-return": {
321
+ consoleRole: "tenant-admin",
322
+ oauthScope: "mcp:write",
323
+ readOnly: false
324
+ },
325
+ "update-return": {
326
+ consoleRole: "tenant-admin",
327
+ oauthScope: "mcp:write",
328
+ readOnly: false
329
+ },
330
+ "return-with-refund": {
331
+ consoleRole: "tenant-admin",
332
+ oauthScope: "mcp:write",
333
+ readOnly: false
334
+ },
335
+ "update-transaction": {
336
+ consoleRole: "tenant-admin",
337
+ oauthScope: "mcp:write",
338
+ readOnly: false
339
+ },
340
+ "update-field-config": {
341
+ consoleRole: "tenant-admin",
342
+ oauthScope: "mcp:write",
343
+ readOnly: false
344
+ },
345
+ "sdk-get-recipe": {
346
+ consoleRole: "tenant-viewer",
347
+ oauthScope: "mcp:read",
348
+ readOnly: true
349
+ },
350
+ "sdk-search-docs": {
351
+ consoleRole: "tenant-viewer",
352
+ oauthScope: "mcp:read",
353
+ readOnly: true
354
+ },
355
+ "sdk-get-auth-setup": {
356
+ consoleRole: "tenant-viewer",
357
+ oauthScope: "mcp:read",
358
+ readOnly: true
359
+ },
360
+ "sdk-get-collection-pattern": {
361
+ consoleRole: "tenant-viewer",
362
+ oauthScope: "mcp:read",
363
+ readOnly: true
364
+ }
365
+ };
366
+ var MCP_TOOL_NAMES = Object.keys(
367
+ MCP_TOOL_CONTRACT
368
+ );
369
+ function isMcpToolName(toolName) {
370
+ return Object.prototype.hasOwnProperty.call(MCP_TOOL_CONTRACT, toolName);
371
+ }
372
+
94
373
  // src/tool-policy.ts
95
374
  var READ_ONLY_ANNOTATION = {
96
375
  readOnly: true,
@@ -347,14 +626,14 @@ var TOOL_POLICY_MANIFEST = {
347
626
  }
348
627
  };
349
628
  function evaluateToolPolicy(toolName, scopes) {
350
- const entry = TOOL_POLICY_MANIFEST[toolName];
351
- if (!entry) {
629
+ if (!isMcpToolName(toolName)) {
352
630
  return {
353
631
  allowed: false,
354
632
  reason: "tool_policy_missing",
355
633
  message: `No tool-policy entry for ${toolName}`
356
634
  };
357
635
  }
636
+ const entry = TOOL_POLICY_MANIFEST[toolName];
358
637
  if (!scopes.includes(entry.oauthScope)) {
359
638
  return {
360
639
  allowed: false,
@@ -365,17 +644,12 @@ function evaluateToolPolicy(toolName, scopes) {
365
644
  return { allowed: true, entry };
366
645
  }
367
646
 
368
- // src/tools/query-collection.ts
369
- import { z } from "zod";
647
+ // src/lib/mcp-telemetry.ts
648
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
649
+ import { randomUUID as randomUUID2 } from "crypto";
370
650
 
371
- // src/lib/client.ts
372
- import {
373
- CollectionClient,
374
- CommunityClient,
375
- ModerationApi,
376
- ServerCommerceClient,
377
- createServerClient
378
- } from "@01.software/sdk";
651
+ // src/lib/console-api.ts
652
+ import { createHash } from "crypto";
379
653
 
380
654
  // src/service-auth.ts
381
655
  import { createPrivateKey, randomUUID, sign as signBytes } from "crypto";
@@ -504,43 +778,209 @@ function signMcpServiceToken(context) {
504
778
  return `${signingInput}.${signature.toString("base64url")}`;
505
779
  }
506
780
 
507
- // src/lib/client.ts
781
+ // src/lib/console-api.ts
782
+ var BASE_URL = process.env.SOFTWARE_API_URL || "http://localhost:3000";
783
+ var TIMEOUT_MS = 5e3;
508
784
  var MISSING_HTTP_AUTH_CONTEXT_ERROR = "MCP HTTP requests require a validated OAuth tenant context before tool execution.";
509
- function getClient() {
785
+ function resolveAuthHeaderContext() {
510
786
  const oauthContext = tenantAuthContext();
511
787
  if (oauthContext) {
512
- const serviceToken = signMcpServiceToken(oauthContext);
513
- const client = {
514
- lastRequestId: null,
515
- commerce: void 0,
516
- collections: void 0,
517
- community: void 0
518
- };
519
- const onRequestId = (id) => {
520
- client.lastRequestId = id;
788
+ return {
789
+ apiKey: signMcpServiceToken(oauthContext),
790
+ mode: "oauth"
521
791
  };
522
- client.commerce = new ServerCommerceClient({
523
- secretKey: serviceToken,
524
- onRequestId
525
- });
526
- client.collections = new CollectionClient(
527
- "",
528
- serviceToken,
529
- void 0,
530
- void 0,
531
- onRequestId
792
+ }
793
+ if (hasRequestContext()) throw new Error(MISSING_HTTP_AUTH_CONTEXT_ERROR);
794
+ return {
795
+ apiKey: process.env.SOFTWARE_SECRET_KEY,
796
+ mode: "stdio",
797
+ publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY ?? process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY
798
+ };
799
+ }
800
+ function resolveApiKey() {
801
+ const { apiKey } = resolveAuthHeaderContext();
802
+ if (!apiKey || typeof apiKey !== "string") {
803
+ throw new Error(
804
+ "Authentication required. Set SOFTWARE_SECRET_KEY for stdio transport."
532
805
  );
533
- const community = new CommunityClient({ secretKey: serviceToken });
534
- const moderation = new ModerationApi({ secretKey: serviceToken, onRequestId });
535
- client.community = Object.assign(community, {
536
- moderation: {
537
- banCustomer: moderation.banCustomer.bind(moderation),
538
- unbanCustomer: moderation.unbanCustomer.bind(moderation)
539
- }
806
+ }
807
+ return apiKey;
808
+ }
809
+ function buildAuthHeaders(apiKey) {
810
+ const { mode, publishableKey } = resolveAuthHeaderContext();
811
+ const headers = {
812
+ Authorization: `Bearer ${apiKey}`
813
+ };
814
+ if (mode === "stdio" && publishableKey) {
815
+ headers["X-Publishable-Key"] = publishableKey;
816
+ }
817
+ return headers;
818
+ }
819
+ function extractErrorMessage(body) {
820
+ if (!body || typeof body !== "object") return void 0;
821
+ const b = body;
822
+ if (typeof b.error === "string") return b.error;
823
+ if (Array.isArray(b.errors) && b.errors[0]?.message) {
824
+ return String(b.errors[0].message);
825
+ }
826
+ if (typeof b.message === "string") return b.message;
827
+ return void 0;
828
+ }
829
+ async function consoleGet(path, apiKey) {
830
+ const authHeaders = buildAuthHeaders(apiKey);
831
+ const controller = new AbortController();
832
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
833
+ try {
834
+ const res = await fetch(`${BASE_URL}${path}`, {
835
+ headers: authHeaders,
836
+ signal: controller.signal
540
837
  });
541
- return client;
838
+ if (!res.ok) {
839
+ const body = await res.json().catch(() => ({}));
840
+ const msg = extractErrorMessage(body);
841
+ throw new Error(msg || `Console GET ${path} failed: ${res.status}`);
842
+ }
843
+ return res.json();
844
+ } finally {
845
+ clearTimeout(timeoutId);
542
846
  }
543
- if (hasRequestContext()) throw new Error(MISSING_HTTP_AUTH_CONTEXT_ERROR);
847
+ }
848
+ async function consolePost(path, body, apiKey) {
849
+ const authHeaders = buildAuthHeaders(apiKey);
850
+ const controller = new AbortController();
851
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
852
+ try {
853
+ const res = await fetch(`${BASE_URL}${path}`, {
854
+ method: "POST",
855
+ headers: { ...authHeaders, "Content-Type": "application/json" },
856
+ body: JSON.stringify(body),
857
+ signal: controller.signal
858
+ });
859
+ if (!res.ok) {
860
+ const errBody = await res.json().catch(() => ({}));
861
+ const msg = extractErrorMessage(errBody);
862
+ throw new Error(msg || `Console POST ${path} failed: ${res.status}`);
863
+ }
864
+ return res.json();
865
+ } finally {
866
+ clearTimeout(timeoutId);
867
+ }
868
+ }
869
+ async function consolePostTelemetry(path, body, apiKey) {
870
+ const authHeaders = buildAuthHeaders(apiKey);
871
+ const controller = new AbortController();
872
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
873
+ try {
874
+ const res = await fetch(`${BASE_URL}${path}`, {
875
+ method: "POST",
876
+ headers: { ...authHeaders, "Content-Type": "application/json" },
877
+ body: JSON.stringify(body),
878
+ signal: controller.signal
879
+ });
880
+ if (!res.ok) {
881
+ const errBody = await res.json().catch(() => ({}));
882
+ const msg = extractErrorMessage(errBody);
883
+ throw new Error(msg || `Console POST ${path} failed: ${res.status}`);
884
+ }
885
+ } finally {
886
+ clearTimeout(timeoutId);
887
+ }
888
+ }
889
+
890
+ // src/lib/mcp-telemetry.ts
891
+ var TELEMETRY_ENDPOINT = "/api/tenants/mcp-telemetry";
892
+ var FLUSH_TIMEOUT_MS = 1500;
893
+ var telemetryContext = new AsyncLocalStorage2();
894
+ function createMcpTelemetrySummary(transport) {
895
+ return {
896
+ sessionId: randomUUID2(),
897
+ startedAtMs: Date.now(),
898
+ successfulWriteCount: 0,
899
+ toolCallCount: 0,
900
+ toolCounts: /* @__PURE__ */ new Map(),
901
+ transport
902
+ };
903
+ }
904
+ function currentMcpTelemetrySummary() {
905
+ return telemetryContext.getStore();
906
+ }
907
+ function runWithMcpTelemetry(summary, fn) {
908
+ return telemetryContext.run(summary, fn);
909
+ }
910
+ function recordMcpToolResult(params) {
911
+ if (!isMcpToolName(params.toolName)) return;
912
+ const toolName = params.toolName;
913
+ params.summary.toolCallCount += 1;
914
+ params.summary.toolCounts.set(
915
+ toolName,
916
+ (params.summary.toolCounts.get(toolName) ?? 0) + 1
917
+ );
918
+ if (!MCP_TOOL_CONTRACT[toolName].readOnly && toolResultSucceeded(params.resultText)) {
919
+ params.summary.successfulWriteCount += 1;
920
+ }
921
+ }
922
+ function toMcpTelemetryBody(summary) {
923
+ if (summary.toolCallCount <= 0) return null;
924
+ const durationMs = summary.transport === "http" ? Math.max(0, summary.durationMs ?? Date.now() - summary.startedAtMs) : void 0;
925
+ return {
926
+ converted: summary.successfulWriteCount > 0,
927
+ ...durationMs !== void 0 ? { durationMs } : {},
928
+ sessionId: summary.sessionId,
929
+ successfulWriteCount: summary.successfulWriteCount,
930
+ toolCallCount: summary.toolCallCount,
931
+ toolCounts: Object.fromEntries(summary.toolCounts),
932
+ transport: summary.transport
933
+ };
934
+ }
935
+ async function flushMcpTelemetrySummary(summary) {
936
+ const body = toMcpTelemetryBody(summary);
937
+ if (!body) return;
938
+ await swallow(
939
+ withTimeout(async () => {
940
+ const apiKey = resolveApiKey();
941
+ await consolePostTelemetry(TELEMETRY_ENDPOINT, body, apiKey);
942
+ }, FLUSH_TIMEOUT_MS)
943
+ );
944
+ }
945
+ function toolResultSucceeded(resultText) {
946
+ if (!resultText) return false;
947
+ try {
948
+ const parsed = JSON.parse(resultText);
949
+ return parsed.success === true;
950
+ } catch {
951
+ return false;
952
+ }
953
+ }
954
+ async function withTimeout(fn, timeoutMs) {
955
+ let timer;
956
+ await Promise.race([
957
+ fn(),
958
+ new Promise((resolve) => {
959
+ timer = setTimeout(resolve, timeoutMs);
960
+ })
961
+ ]);
962
+ if (timer) clearTimeout(timer);
963
+ }
964
+ async function swallow(promise) {
965
+ try {
966
+ await promise;
967
+ } catch {
968
+ }
969
+ }
970
+
971
+ // src/tools/query-collection.ts
972
+ import { z as z3 } from "zod";
973
+
974
+ // src/lib/client.ts
975
+ import { createServerClient } from "@01.software/sdk";
976
+ var MISSING_HTTP_AUTH_CONTEXT_ERROR2 = "MCP HTTP requests require a validated OAuth tenant context before tool execution.";
977
+ var HTTP_OAUTH_SDK_CLIENT_ERROR = "MCP HTTP OAuth requests cannot use SDK-backed tools. Use reviewed Console service endpoints for OAuth transport.";
978
+ function getClient() {
979
+ const oauthContext = tenantAuthContext();
980
+ if (oauthContext) {
981
+ throw new Error(HTTP_OAUTH_SDK_CLIENT_ERROR);
982
+ }
983
+ if (hasRequestContext()) throw new Error(MISSING_HTTP_AUTH_CONTEXT_ERROR2);
544
984
  const secretKey = process.env.SOFTWARE_SECRET_KEY;
545
985
  const publishableKey = process.env.SOFTWARE_PUBLISHABLE_KEY || process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY;
546
986
  if (!secretKey) {
@@ -563,15 +1003,18 @@ function getClient() {
563
1003
  }
564
1004
 
565
1005
  // src/tools/query-collection.ts
566
- import { COLLECTIONS } from "@01.software/sdk";
1006
+ import { SERVER_COLLECTIONS } from "@01.software/sdk";
567
1007
  var schema = {
568
- collection: z.enum(COLLECTIONS).describe("Collection name (required)"),
569
- where: z.string().optional().describe(
1008
+ collection: z3.enum(SERVER_COLLECTIONS).describe("Collection name (required)"),
1009
+ where: z3.string().optional().describe(
570
1010
  `Filter conditions (JSON string, optional). Pass the Payload query condition object as a JSON string. Example: '{"title":{"equals":"Product name"}}'`
571
1011
  ),
572
- limit: z.number().min(1).max(100).default(10).describe("Maximum number of items to return (1-100, default: 10)."),
573
- page: z.number().optional().describe("Page number (optional). Starts from 1. Used for pagination."),
574
- sort: z.string().regex(/^-?[a-zA-Z0-9_.]+$/, 'Sort must be a field name, optionally prefixed with "-" for descending').optional().describe(
1012
+ limit: z3.number().min(1).max(100).default(10).describe("Maximum number of items to return (1-100, default: 10)."),
1013
+ page: z3.number().optional().describe("Page number (optional). Starts from 1. Used for pagination."),
1014
+ sort: z3.string().regex(
1015
+ /^-?[a-zA-Z0-9_.]+$/,
1016
+ 'Sort must be a field name, optionally prefixed with "-" for descending'
1017
+ ).optional().describe(
575
1018
  'Sort field (optional). Use "fieldName" for ascending or "-fieldName" for descending. Example: "createdAt" or "-createdAt"'
576
1019
  )
577
1020
  };
@@ -625,11 +1068,11 @@ async function queryCollection({
625
1068
  }
626
1069
 
627
1070
  // src/tools/get-collection-by-id.ts
628
- import { z as z2 } from "zod";
629
- import { COLLECTIONS as COLLECTIONS2 } from "@01.software/sdk";
1071
+ import { z as z4 } from "zod";
1072
+ import { SERVER_COLLECTIONS as SERVER_COLLECTIONS2 } from "@01.software/sdk";
630
1073
  var schema2 = {
631
- collection: z2.enum(COLLECTIONS2).describe("Collection name (required)"),
632
- id: z2.string().min(1).describe("Item ID (required)")
1074
+ collection: z4.enum(SERVER_COLLECTIONS2).describe("Collection name (required)"),
1075
+ id: z4.string().min(1).describe("Item ID (required)")
633
1076
  };
634
1077
  var metadata2 = {
635
1078
  name: "get-collection-by-id",
@@ -655,9 +1098,9 @@ async function getCollectionById({
655
1098
  }
656
1099
 
657
1100
  // src/tools/get-order.ts
658
- import { z as z3 } from "zod";
1101
+ import { z as z5 } from "zod";
659
1102
  var schema3 = {
660
- orderNumber: z3.string().min(1).describe("Order number to look up (required)")
1103
+ orderNumber: z5.string().min(1).describe("Order number to look up (required)")
661
1104
  };
662
1105
  var metadata3 = {
663
1106
  name: "get-order",
@@ -687,24 +1130,24 @@ async function getOrder({
687
1130
  }
688
1131
 
689
1132
  // src/tools/create-order.ts
690
- import { z as z4 } from "zod";
1133
+ import { z as z6 } from "zod";
691
1134
  var schema4 = {
692
- pgPaymentId: z4.string().optional().describe("PG payment ID (optional \u2014 omit for free orders)"),
693
- orderNumber: z4.string().min(1).describe("Unique order number (required)"),
694
- customerSnapshot: z4.object({
695
- name: z4.string().optional().describe("Customer name"),
696
- email: z4.string().describe("Customer email (required)"),
697
- phone: z4.string().optional().describe("Customer phone")
1135
+ pgPaymentId: z6.string().optional().describe("PG payment ID (optional \u2014 omit for free orders)"),
1136
+ orderNumber: z6.string().min(1).describe("Unique order number (required)"),
1137
+ customerSnapshot: z6.object({
1138
+ name: z6.string().optional().describe("Customer name"),
1139
+ email: z6.string().describe("Customer email (required)"),
1140
+ phone: z6.string().optional().describe("Customer phone")
698
1141
  }).describe("Customer snapshot at time of order (required)"),
699
- shippingAddress: z4.record(z4.string(), z4.unknown()).describe(
1142
+ shippingAddress: z6.record(z6.string(), z6.unknown()).describe(
700
1143
  "Shipping address object (required). Fields: postalCode, address1, address2, deliveryMessage, recipientName, phone"
701
1144
  ),
702
- orderItems: z4.array(z4.record(z4.string(), z4.unknown())).describe(
1145
+ orderItems: z6.array(z6.record(z6.string(), z6.unknown())).describe(
703
1146
  "Array of order item objects (required). Each: { product, variant, option, quantity, unitPrice?, totalPrice? }"
704
1147
  ),
705
- totalAmount: z4.number().nonnegative().describe("Total order amount (required, min 0)"),
706
- shippingAmount: z4.number().nonnegative().optional().describe("Shipping amount (optional, default 0)"),
707
- discountCode: z4.string().optional().describe("Discount code to apply (optional)")
1148
+ totalAmount: z6.number().nonnegative().describe("Total order amount (required, min 0)"),
1149
+ shippingAmount: z6.number().nonnegative().optional().describe("Shipping amount (optional, default 0)"),
1150
+ discountCode: z6.string().optional().describe("Discount code to apply (optional)")
708
1151
  };
709
1152
  var metadata4 = {
710
1153
  name: "create-order",
@@ -729,10 +1172,10 @@ async function createOrder(params) {
729
1172
  }
730
1173
 
731
1174
  // src/tools/update-order.ts
732
- import { z as z5 } from "zod";
1175
+ import { z as z7 } from "zod";
733
1176
  var schema5 = {
734
- orderNumber: z5.string().min(1).describe("Order number (required)"),
735
- status: z5.enum([
1177
+ orderNumber: z7.string().min(1).describe("Order number (required)"),
1178
+ status: z7.enum([
736
1179
  "pending",
737
1180
  "paid",
738
1181
  "failed",
@@ -769,15 +1212,15 @@ async function updateOrder({
769
1212
  }
770
1213
 
771
1214
  // src/tools/checkout.ts
772
- import { z as z6 } from "zod";
1215
+ import { z as z8 } from "zod";
773
1216
  var schema6 = {
774
- cartId: z6.string().min(1).describe("Cart ID to convert to order (required)"),
775
- pgPaymentId: z6.string().optional().describe("PG payment ID (optional \u2014 omit for free orders)"),
776
- orderNumber: z6.string().min(1).describe("Unique order number (required)"),
777
- customerSnapshot: z6.record(z6.string(), z6.unknown()).describe(
1217
+ cartId: z8.string().min(1).describe("Cart ID to convert to order (required)"),
1218
+ pgPaymentId: z8.string().optional().describe("PG payment ID (optional \u2014 omit for free orders)"),
1219
+ orderNumber: z8.string().min(1).describe("Unique order number (required)"),
1220
+ customerSnapshot: z8.record(z8.string(), z8.unknown()).describe(
778
1221
  "Customer snapshot object (required). Fields: { name?, email, phone? }"
779
1222
  ),
780
- discountCode: z6.string().optional().describe("Discount code to apply (optional)")
1223
+ discountCode: z8.string().optional().describe("Discount code to apply (optional)")
781
1224
  };
782
1225
  var metadata6 = {
783
1226
  name: "checkout",
@@ -802,17 +1245,17 @@ async function checkout(params) {
802
1245
  }
803
1246
 
804
1247
  // src/tools/create-fulfillment.ts
805
- import { z as z7 } from "zod";
1248
+ import { z as z9 } from "zod";
806
1249
  var schema7 = {
807
- orderNumber: z7.string().min(1).describe("Order number (required)"),
808
- carrier: z7.string().optional().describe("Shipping carrier name (optional)"),
809
- trackingNumber: z7.string().optional().describe(
1250
+ orderNumber: z9.string().min(1).describe("Order number (required)"),
1251
+ carrier: z9.string().optional().describe("Shipping carrier name (optional)"),
1252
+ trackingNumber: z9.string().optional().describe(
810
1253
  'Tracking number (optional). Setting carrier + tracking triggers "shipped" status'
811
1254
  ),
812
- items: z7.array(
813
- z7.object({
814
- orderItem: z7.string().min(1).describe("Order item ID"),
815
- quantity: z7.number().int().positive().describe("Quantity to fulfill")
1255
+ items: z9.array(
1256
+ z9.object({
1257
+ orderItem: z9.string().min(1).describe("Order item ID"),
1258
+ quantity: z9.number().int().positive().describe("Quantity to fulfill")
816
1259
  })
817
1260
  ).describe("Array of items to fulfill (required)")
818
1261
  };
@@ -847,16 +1290,16 @@ async function createFulfillment({
847
1290
  }
848
1291
 
849
1292
  // src/tools/update-fulfillment.ts
850
- import { z as z8 } from "zod";
1293
+ import { z as z10 } from "zod";
851
1294
  var schema8 = {
852
- fulfillmentId: z8.string().min(1).describe("Fulfillment ID (required)"),
853
- status: z8.enum(["packed", "shipped", "delivered", "failed"]).describe(
1295
+ fulfillmentId: z10.string().min(1).describe("Fulfillment ID (required)"),
1296
+ status: z10.enum(["packed", "shipped", "delivered", "failed"]).describe(
854
1297
  "New fulfillment status (required). FSM: pending\u2192packed/shipped/failed, packed\u2192shipped/failed, shipped\u2192delivered/failed"
855
1298
  ),
856
- carrier: z8.string().optional().describe(
1299
+ carrier: z10.string().optional().describe(
857
1300
  "Shipping carrier (optional, changeable only in pending/packed status)"
858
1301
  ),
859
- trackingNumber: z8.string().optional().describe(
1302
+ trackingNumber: z10.string().optional().describe(
860
1303
  "Tracking number (optional, changeable only in pending/packed status)"
861
1304
  )
862
1305
  };
@@ -890,131 +1333,6 @@ async function updateFulfillment({
890
1333
  }
891
1334
  }
892
1335
 
893
- // ../../packages/contracts/src/tenant/index.ts
894
- import { z as z9 } from "zod";
895
- var tenantFieldConfigStateSchema = z9.object({
896
- hiddenFields: z9.array(z9.string()),
897
- isHidden: z9.boolean()
898
- }).strict();
899
- var tenantContextQuerySchema = z9.object({
900
- counts: z9.literal("true").optional()
901
- }).strict();
902
- var tenantContextToolInputSchema = z9.object({
903
- includeCounts: z9.boolean().optional().default(false).describe(
904
- "Include per-collection document counts and config status (bypasses cache, slower)"
905
- )
906
- }).strict();
907
- var tenantContextResponseSchema = z9.object({
908
- tenant: z9.object({
909
- id: z9.string(),
910
- name: z9.string(),
911
- plan: z9.string(),
912
- planSource: z9.string().optional(),
913
- authoritative: z9.boolean().optional(),
914
- capabilityVersion: z9.string().optional(),
915
- isDevMode: z9.boolean()
916
- }).strict(),
917
- features: z9.array(z9.string()),
918
- collections: z9.object({
919
- active: z9.array(z9.string()),
920
- inactive: z9.array(z9.string())
921
- }).strict(),
922
- fieldConfigs: z9.record(z9.string(), tenantFieldConfigStateSchema),
923
- counts: z9.record(z9.string(), z9.number()).optional(),
924
- config: z9.object({
925
- webhookConfigured: z9.boolean()
926
- }).strict().optional()
927
- }).strict();
928
- var COLLECTION_SCHEMA_CONTRACT_VERSION = 1;
929
- var collectionSchemaEndpointParamsSchema = z9.object({
930
- collectionSlug: z9.string().min(1, "collectionSlug is required")
931
- }).strict();
932
- function createCollectionSchemaToolInputSchema(collections) {
933
- return z9.object({
934
- collection: z9.enum(collections).describe("Collection name (required)")
935
- }).strict();
936
- }
937
- var collectionFieldOptionSchema = z9.object({
938
- label: z9.string(),
939
- value: z9.string()
940
- }).strict();
941
- var collectionFieldSchema = z9.lazy(
942
- () => z9.object({
943
- name: z9.string(),
944
- path: z9.string(),
945
- type: z9.string(),
946
- required: z9.literal(true).optional(),
947
- unique: z9.literal(true).optional(),
948
- hasMany: z9.literal(true).optional(),
949
- relationTo: z9.union([z9.string(), z9.array(z9.string())]).optional(),
950
- options: z9.array(collectionFieldOptionSchema).optional(),
951
- hidden: z9.literal(true).optional(),
952
- systemManaged: z9.literal(true).optional(),
953
- writable: z9.boolean().optional(),
954
- fields: z9.array(collectionFieldSchema).optional()
955
- }).strict()
956
- );
957
- var collectionSchemaResponseSchema = z9.object({
958
- contractVersion: z9.literal(COLLECTION_SCHEMA_CONTRACT_VERSION),
959
- mode: z9.literal("effective"),
960
- collection: z9.object({
961
- slug: z9.string(),
962
- timestamps: z9.boolean(),
963
- alwaysActive: z9.boolean(),
964
- feature: z9.string().nullable(),
965
- systemFields: z9.array(z9.string()),
966
- visibility: z9.object({
967
- collectionHidden: z9.boolean(),
968
- hiddenFields: z9.array(z9.string())
969
- }).strict(),
970
- fields: z9.array(collectionFieldSchema)
971
- }).strict()
972
- }).strict();
973
-
974
- // ../../packages/contracts/src/ecommerce/index.ts
975
- import { z as z10 } from "zod";
976
- var transactionStatusSchema = z10.enum([
977
- "pending",
978
- "paid",
979
- "failed",
980
- "canceled"
981
- ]);
982
- var updateTransactionSchema = z10.object({
983
- pgPaymentId: z10.string().min(1, "pgPaymentId is required").describe("PG payment ID (required)"),
984
- status: transactionStatusSchema.describe(
985
- "New transaction status (required)"
986
- ),
987
- paymentMethod: z10.string().optional().describe("Payment method (optional)"),
988
- receiptUrl: z10.string().optional().describe("Receipt URL (optional)"),
989
- paymentKey: z10.string().min(1).optional().describe("Provider payment key for verified paid confirmation"),
990
- amount: z10.number().int().positive().optional().describe("Provider-confirmed amount for verified paid confirmation")
991
- }).strict();
992
- var UpdateTransactionSchema = updateTransactionSchema;
993
- var returnReasonSchema = z10.enum([
994
- "change_of_mind",
995
- "defective",
996
- "wrong_delivery",
997
- "damaged",
998
- "other"
999
- ]);
1000
- var restockActionSchema = z10.enum(["return_to_stock", "discard"]);
1001
- var returnWithRefundItemSchema = z10.object({
1002
- orderItem: z10.union([z10.string(), z10.number()]).transform(String),
1003
- quantity: z10.number().int().positive("quantity must be a positive integer"),
1004
- restockAction: restockActionSchema.default("return_to_stock")
1005
- }).strict();
1006
- var returnWithRefundSchema = z10.object({
1007
- orderNumber: z10.string().min(1, "orderNumber is required").describe("Order number (required)"),
1008
- reason: returnReasonSchema.optional().describe("Return reason (optional)"),
1009
- reasonDetail: z10.string().optional().describe("Detailed reason text (optional)"),
1010
- returnItems: z10.array(returnWithRefundItemSchema).min(1, "At least one return item is required").max(100, "Too many return items").describe("Array of products to return (required)"),
1011
- refundAmount: z10.number().min(0, "refundAmount must be non-negative").describe("Refund amount (required, min 0)"),
1012
- pgPaymentId: z10.string().min(1, "pgPaymentId is required").describe("PG payment ID for refund (required)"),
1013
- paymentKey: z10.string().min(1).optional().describe("Provider payment key for verified refund"),
1014
- refundReceiptUrl: z10.string().optional().describe("Refund receipt URL (optional)")
1015
- }).strict();
1016
- var ReturnWithRefundSchema = returnWithRefundSchema;
1017
-
1018
1336
  // src/tools/update-transaction.ts
1019
1337
  var schema9 = UpdateTransactionSchema.shape;
1020
1338
  var metadata9 = {
@@ -1452,97 +1770,7 @@ async function stockCheck({
1452
1770
  }
1453
1771
 
1454
1772
  // src/tools/get-collection-schema.ts
1455
- import { COLLECTIONS as COLLECTIONS3 } from "@01.software/sdk";
1456
-
1457
- // src/lib/console-api.ts
1458
- import { createHash } from "crypto";
1459
- var BASE_URL = process.env.SOFTWARE_API_URL || "http://localhost:3000";
1460
- var TIMEOUT_MS = 5e3;
1461
- var MISSING_HTTP_AUTH_CONTEXT_ERROR2 = "MCP HTTP requests require a validated OAuth tenant context before tool execution.";
1462
- function resolveAuthHeaderContext() {
1463
- const oauthContext = tenantAuthContext();
1464
- if (oauthContext) {
1465
- return {
1466
- apiKey: signMcpServiceToken(oauthContext),
1467
- mode: "oauth"
1468
- };
1469
- }
1470
- if (hasRequestContext()) throw new Error(MISSING_HTTP_AUTH_CONTEXT_ERROR2);
1471
- return {
1472
- apiKey: process.env.SOFTWARE_SECRET_KEY,
1473
- mode: "stdio",
1474
- publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY ?? process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY
1475
- };
1476
- }
1477
- function resolveApiKey() {
1478
- const { apiKey } = resolveAuthHeaderContext();
1479
- if (!apiKey || typeof apiKey !== "string") {
1480
- throw new Error(
1481
- "Authentication required. Set SOFTWARE_SECRET_KEY for stdio transport."
1482
- );
1483
- }
1484
- return apiKey;
1485
- }
1486
- function buildAuthHeaders(apiKey) {
1487
- const { mode, publishableKey } = resolveAuthHeaderContext();
1488
- const headers = {
1489
- Authorization: `Bearer ${apiKey}`
1490
- };
1491
- if (mode === "stdio" && publishableKey) {
1492
- headers["X-Publishable-Key"] = publishableKey;
1493
- }
1494
- return headers;
1495
- }
1496
- function extractErrorMessage(body) {
1497
- if (!body || typeof body !== "object") return void 0;
1498
- const b = body;
1499
- if (typeof b.error === "string") return b.error;
1500
- if (Array.isArray(b.errors) && b.errors[0]?.message) {
1501
- return String(b.errors[0].message);
1502
- }
1503
- if (typeof b.message === "string") return b.message;
1504
- return void 0;
1505
- }
1506
- async function consoleGet(path, apiKey) {
1507
- const authHeaders = buildAuthHeaders(apiKey);
1508
- const controller = new AbortController();
1509
- const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
1510
- try {
1511
- const res = await fetch(`${BASE_URL}${path}`, {
1512
- headers: authHeaders,
1513
- signal: controller.signal
1514
- });
1515
- if (!res.ok) {
1516
- const body = await res.json().catch(() => ({}));
1517
- const msg = extractErrorMessage(body);
1518
- throw new Error(msg || `Console GET ${path} failed: ${res.status}`);
1519
- }
1520
- return res.json();
1521
- } finally {
1522
- clearTimeout(timeoutId);
1523
- }
1524
- }
1525
- async function consolePost(path, body, apiKey) {
1526
- const authHeaders = buildAuthHeaders(apiKey);
1527
- const controller = new AbortController();
1528
- const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
1529
- try {
1530
- const res = await fetch(`${BASE_URL}${path}`, {
1531
- method: "POST",
1532
- headers: { ...authHeaders, "Content-Type": "application/json" },
1533
- body: JSON.stringify(body),
1534
- signal: controller.signal
1535
- });
1536
- if (!res.ok) {
1537
- const errBody = await res.json().catch(() => ({}));
1538
- const msg = extractErrorMessage(errBody);
1539
- throw new Error(msg || `Console POST ${path} failed: ${res.status}`);
1540
- }
1541
- return res.json();
1542
- } finally {
1543
- clearTimeout(timeoutId);
1544
- }
1545
- }
1773
+ import { SERVER_COLLECTIONS as SERVER_COLLECTIONS3 } from "@01.software/sdk";
1546
1774
 
1547
1775
  // src/lib/collection-schema.ts
1548
1776
  async function getCollectionSchema(collection) {
@@ -1555,7 +1783,7 @@ async function getCollectionSchema(collection) {
1555
1783
  }
1556
1784
 
1557
1785
  // src/tools/get-collection-schema.ts
1558
- var schema22 = createCollectionSchemaToolInputSchema(COLLECTIONS3).shape;
1786
+ var schema22 = createCollectionSchemaToolInputSchema(SERVER_COLLECTIONS3).shape;
1559
1787
  var metadata22 = {
1560
1788
  name: "get-collection-schema",
1561
1789
  description: "Get the authoritative tenant-aware collection schema from console. Use this before create/update to understand writable fields, hidden fields, required metadata, and collection-level visibility.",
@@ -2275,7 +2503,7 @@ var docIndex = [
2275
2503
  {
2276
2504
  title: "Browser Client vs Server Client",
2277
2505
  keywords: ["browser", "server", "publishable key", "secret key", "createClient", "createServerClient", "NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY", "SOFTWARE_PUBLISHABLE_KEY", "SOFTWARE_SECRET_KEY", "pk01_", "sk01_", "pat01_", "read-only", "full crud"],
2278
- summary: "createClient() for browser with publishableKey (read-only pk01_), createServerClient() for server with SOFTWARE_SECRET_KEY (usually sk01_, sometimes pat01_ in browser-auth-scoped flows). Never expose SECRET_KEY to the browser.",
2506
+ summary: "createClient() for browser with publishableKey (read-only pk01_), createServerClient() for server with matching SOFTWARE_PUBLISHABLE_KEY + SOFTWARE_SECRET_KEY (usually sk01_, sometimes pat01_ in scoped flows). Never expose SECRET_KEY to the browser.",
2279
2507
  resourceUri: "docs://sdk/getting-started"
2280
2508
  },
2281
2509
  // Query Builder
@@ -2641,9 +2869,9 @@ function handler4({
2641
2869
 
2642
2870
  // src/tools/sdk-get-collection-pattern.ts
2643
2871
  import { z as z27 } from "zod";
2644
- import { COLLECTIONS as COLLECTIONS4 } from "@01.software/sdk";
2872
+ import { COLLECTIONS, SERVER_COLLECTIONS as SERVER_COLLECTIONS4 } from "@01.software/sdk";
2645
2873
  var schema29 = {
2646
- collection: z27.enum(COLLECTIONS4).describe("Collection name"),
2874
+ collection: z27.enum(SERVER_COLLECTIONS4).describe("Collection name"),
2647
2875
  operation: z27.enum(["read", "write", "full-crud"]).default("read").describe("What operations are needed"),
2648
2876
  surface: z27.enum(["query-builder", "react-query", "server-api"]).default("query-builder").describe("Preferred API surface")
2649
2877
  };
@@ -2658,7 +2886,15 @@ var metadata29 = {
2658
2886
  }
2659
2887
  };
2660
2888
  function generatePattern(collection, operation, surface) {
2889
+ const isPublicCollection = COLLECTIONS.includes(
2890
+ collection
2891
+ );
2661
2892
  if (surface === "react-query") {
2893
+ if (!isPublicCollection) {
2894
+ throw new Error(
2895
+ `${collection} is server-only. Use surface="server-api" with createServerClient().`
2896
+ );
2897
+ }
2662
2898
  const parts2 = [];
2663
2899
  if (operation === "read") {
2664
2900
  parts2.push(
@@ -2770,17 +3006,29 @@ function generatePattern(collection, operation, surface) {
2770
3006
  }
2771
3007
  const parts = [];
2772
3008
  if (operation === "read" || operation === "full-crud") {
2773
- parts.push(
2774
- `// Read with any client (Client or ServerClient)`,
2775
- `const result = await client.collections.from('${collection}').find({`,
2776
- ` where: { status: { equals: 'published' } },`,
2777
- ` limit: 10,`,
2778
- ` sort: '-createdAt'`,
2779
- `})`,
2780
- ``,
2781
- `const item = await client.collections.from('${collection}').findById(id)`,
2782
- `const { totalDocs } = await client.collections.from('${collection}').count()`
2783
- );
3009
+ if (isPublicCollection) {
3010
+ parts.push(
3011
+ `// Read with any client (Client or ServerClient)`,
3012
+ `const result = await client.collections.from('${collection}').find({`,
3013
+ ` where: { status: { equals: 'published' } },`,
3014
+ ` limit: 10,`,
3015
+ ` sort: '-createdAt'`,
3016
+ `})`,
3017
+ ``,
3018
+ `const item = await client.collections.from('${collection}').findById(id)`,
3019
+ `const { totalDocs } = await client.collections.from('${collection}').count()`
3020
+ );
3021
+ } else {
3022
+ parts.push(
3023
+ `// Server-only collection: use ServerClient`,
3024
+ `const result = await serverClient.collections.from('${collection}').find({`,
3025
+ ` limit: 10,`,
3026
+ ` sort: '-createdAt'`,
3027
+ `})`,
3028
+ ``,
3029
+ `const item = await serverClient.collections.from('${collection}').findById(id)`
3030
+ );
3031
+ }
2784
3032
  }
2785
3033
  if (operation === "write" || operation === "full-crud") {
2786
3034
  parts.push(
@@ -2795,6 +3043,7 @@ function generatePattern(collection, operation, surface) {
2795
3043
  code: parts.join("\n"),
2796
3044
  notes: [
2797
3045
  "Query Builder works with both Client and ServerClient for reads",
3046
+ !isPublicCollection ? "This collection is server-only" : "",
2798
3047
  operation !== "read" ? "Write operations require ServerClient" : ""
2799
3048
  ].filter(Boolean)
2800
3049
  };
@@ -2972,9 +3221,9 @@ You can perform the "${goal}" task by following the patterns above.`;
2972
3221
 
2973
3222
  // src/prompts/collection-query-help.ts
2974
3223
  import { z as z29 } from "zod";
2975
- import { COLLECTIONS as COLLECTIONS5 } from "@01.software/sdk";
3224
+ import { COLLECTIONS as COLLECTIONS2, SERVER_COLLECTIONS as SERVER_COLLECTIONS5 } from "@01.software/sdk";
2976
3225
  var schema31 = {
2977
- collection: z29.enum(COLLECTIONS5).describe("Collection name"),
3226
+ collection: z29.enum(SERVER_COLLECTIONS5).describe("Collection name"),
2978
3227
  operation: z29.enum(["find", "create", "update", "delete"]).describe("Operation to perform (find, create, update, delete)"),
2979
3228
  filters: z29.string().optional().describe("Filter conditions (JSON string, optional)")
2980
3229
  };
@@ -2995,6 +3244,11 @@ Filter conditions:
2995
3244
  \`\`\`json
2996
3245
  ${filters}
2997
3246
  \`\`\`` : "";
3247
+ const isPublicCollection = COLLECTIONS2.includes(
3248
+ collection
3249
+ );
3250
+ const readClientName = isPublicCollection ? "client" : "serverClient";
3251
+ const readClientNote = isPublicCollection ? "Client or ServerClient" : "ServerClient only";
2998
3252
  return `How to perform "${operation}" operation on "${collection}" collection:${filterExample}
2999
3253
 
3000
3254
  ## Collection: ${collection}
@@ -3005,26 +3259,26 @@ ${filters}
3005
3259
  \`\`\`typescript
3006
3260
  import { createClient, createServerClient } from '@01.software/sdk'
3007
3261
 
3008
- // Client (read-only)
3262
+ // Client (read-only public collections)
3009
3263
  const client = createClient({
3010
3264
  publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!
3011
3265
  })
3012
3266
 
3013
- // Server client (full CRUD)
3267
+ // Server client (server/public collections, full CRUD)
3014
3268
  const serverClient = createServerClient({
3015
3269
  publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY!,
3016
3270
  secretKey: process.env.SOFTWARE_SECRET_KEY!
3017
3271
  })
3018
3272
 
3019
- ${operation === "find" ? `// Query ${collection} collection with Query Builder
3273
+ ${operation === "find" ? `// Query ${collection} collection with Query Builder (${readClientNote})
3020
3274
  // find() returns PayloadFindResponse with docs array and pagination
3021
- const result = await client.collections.from('${collection}').find(${filters ? `{
3275
+ const result = await ${readClientName}.collections.from('${collection}').find(${filters ? `{
3022
3276
  where: ${filters}
3023
3277
  }` : ""})
3024
3278
  // result.docs - array of items
3025
3279
  // result.totalDocs, result.page, result.totalPages, result.hasNextPage, ...
3026
3280
 
3027
- // Using React Hook
3281
+ ${isPublicCollection ? `// Using React Hook
3028
3282
  const { data, isLoading, error } = client.query.useQuery({
3029
3283
  collection: '${collection}',
3030
3284
  options: { limit: 10 }
@@ -3034,19 +3288,19 @@ const { data, isLoading, error } = client.query.useQuery({
3034
3288
  const { data } = client.query.useSuspenseQuery({
3035
3289
  collection: '${collection}',
3036
3290
  options: { limit: 10 }
3037
- })` : operation === "create" ? `// Create ${collection} item (ServerClient only)
3291
+ })` : `// React hooks are browser/public only and do not support '${collection}'.`}` : operation === "create" ? `// Create ${collection} item (ServerClient only)
3038
3292
  // create() returns PayloadMutationResponse with doc and message
3039
- const result = await serverClient.from('${collection}').create({
3293
+ const result = await serverClient.collections.from('${collection}').create({
3040
3294
  // Enter fields
3041
3295
  })
3042
3296
  // result.doc - created item, result.message` : operation === "update" ? `// Update ${collection} item (ServerClient only)
3043
3297
  // update() returns PayloadMutationResponse with doc and message
3044
- const result = await serverClient.from('${collection}').update(id, {
3298
+ const result = await serverClient.collections.from('${collection}').update(id, {
3045
3299
  // Fields to update
3046
3300
  })
3047
3301
  // result.doc - updated item, result.message` : `// Delete ${collection} item (ServerClient only)
3048
3302
  // remove() returns the deleted document directly
3049
- await serverClient.from('${collection}').remove(id)`}
3303
+ await serverClient.collections.from('${collection}').remove(id)`}
3050
3304
  \`\`\`
3051
3305
 
3052
3306
  ### Useful Tips
@@ -3054,7 +3308,7 @@ await serverClient.from('${collection}').remove(id)`}
3054
3308
  ${operation === "find" ? `- Use \`where\` option for filtering (Payload query syntax)
3055
3309
  - Use \`limit\` and \`page\` for pagination
3056
3310
  - Use \`sort\` for sorting (prefix with "-" for descending)
3057
- - React Query hooks: useQuery, useSuspenseQuery, useInfiniteQuery
3311
+ - ${isPublicCollection ? "React Query hooks: useQuery, useSuspenseQuery, useInfiniteQuery" : "React Query hooks are browser/public only; use ServerClient for this collection"}
3058
3312
  - Cache utilities: invalidateQueries, prefetchQuery, getQueryData, setQueryData` : operation === "create" ? `- Requires ServerClient with secretKey
3059
3313
  - Check required fields
3060
3314
  - TypeScript recommended for type safety` : operation === "update" ? `- Requires ServerClient with secretKey
@@ -3290,7 +3544,7 @@ var FEATURES = {
3290
3544
  ### Required Collections (count > 0)
3291
3545
 
3292
3546
  1. **products** \u2014 Create via Console UI or SDK \`client.collections.from('products').create({ ... })\`
3293
- - Minimum fields: \`{ title, slug, status: 'published', _status: 'published' }\`
3547
+ - Minimum fields: \`{ title, slug, status: 'published' }\`
3294
3548
 
3295
3549
  2. **product-variants** \u2014 At least 1 sellable variant per product
3296
3550
  - Minimum fields: \`{ product, title, price, stock }\`
@@ -3323,7 +3577,8 @@ customer-addresses
3323
3577
 
3324
3578
  ### Optional Collections
3325
3579
 
3326
- customer-groups \u2014 Create via Console UI or SDK \`client.collections.from('customer-groups').create({ title })\`
3580
+ - customer-groups \u2014 Console/server-scoped segmentation for VIP coupons and campaigns. Use \`createServerClient().collections.from('customer-groups')\`.
3581
+ - customer-profile-lists \u2014 Public profile display/ranking lists for storefronts. Browser reads may use \`client.collections.from('customer-profile-lists')\`.
3327
3582
 
3328
3583
  ### Config
3329
3584
 
@@ -3334,10 +3589,10 @@ customer-groups \u2014 Create via Console UI or SDK \`client.collections.from('c
3334
3589
  ### Required Collections (count > 0)
3335
3590
 
3336
3591
  1. **articles** \u2014 At least 1 article
3337
- - Minimum fields: \`{ title, slug }\`
3592
+ - Minimum fields: \`{ title, slug, status: 'published' }\`
3338
3593
 
3339
3594
  2. **article-authors** \u2014 At least 1 author
3340
- - Minimum fields: \`{ title, slug }\`
3595
+ - Minimum fields: \`{ title, slug, status: 'published' }\`
3341
3596
  - Link authors to articles via the \`authors\` relationship field
3342
3597
 
3343
3598
  ### Optional Collections
@@ -3352,7 +3607,7 @@ article-categories, article-tags`,
3352
3607
 
3353
3608
  2. **document-types** \u2014 At least 1 type
3354
3609
  - Minimum fields: \`{ title, slug }\`
3355
- - Link document type via \`documentType\` relationship field
3610
+ - Link document type via \`type\` relationship field
3356
3611
 
3357
3612
  ### Optional Collections
3358
3613
 
@@ -3362,10 +3617,10 @@ document-categories`,
3362
3617
  ### Required Collections (count > 0)
3363
3618
 
3364
3619
  1. **playlists** \u2014 At least 1 playlist
3365
- - Minimum fields: \`{ title, slug, status: 'published', _status: 'published' }\`
3620
+ - Minimum fields: \`{ title, slug, status: 'published' }\`
3366
3621
 
3367
3622
  2. **tracks** \u2014 At least 1 track
3368
- - Minimum fields: \`{ title, sourceUrl, status: 'published', _status: 'published' }\`
3623
+ - Minimum fields: \`{ title, sourceUrl, status: 'published' }\`
3369
3624
 
3370
3625
  3. **playlists.tracks** \u2014 Link at least 1 track from a playlist
3371
3626
  - Minimum fields: \`{ tracks: [trackId] }\`
@@ -3378,11 +3633,11 @@ playlist-categories, playlist-tags, track-categories, track-tags, track-assets`,
3378
3633
  ### Required Collections (count > 0)
3379
3634
 
3380
3635
  1. **galleries** \u2014 At least 1 gallery
3381
- - Minimum fields: \`{ title, slug, status: 'published', _status: 'published' }\`
3636
+ - Minimum fields: \`{ title, slug, status: 'published' }\`
3382
3637
 
3383
3638
  2. **gallery-items** \u2014 At least 1 item per gallery
3384
3639
  - References \`images\` collection (non-upload)
3385
- - Minimum fields: \`{ gallery, image, _status: 'published' }\`
3640
+ - Minimum fields: \`{ gallery, image, status: 'published' }\`
3386
3641
 
3387
3642
  ### Optional Collections
3388
3643
 
@@ -3392,7 +3647,7 @@ gallery-categories, gallery-tags`,
3392
3647
  ### Required Collections (count > 0)
3393
3648
 
3394
3649
  1. **links** \u2014 At least 1 link
3395
- - Minimum fields: \`{ title, slug, url, status: 'published', _status: 'published' }\`
3650
+ - Minimum fields: \`{ title, slug, url, status: 'published' }\`
3396
3651
 
3397
3652
  ### Optional Collections
3398
3653
 
@@ -3464,9 +3719,11 @@ comments, reactions, bookmarks, reports, community-bans
3464
3719
 
3465
3720
  ### Optional Collections
3466
3721
 
3467
- post-categories`
3722
+ post-categories, customer-profile-lists`
3468
3723
  };
3469
- function featureSetupGuide({ feature }) {
3724
+ function featureSetupGuide({
3725
+ feature
3726
+ }) {
3470
3727
  return `# Feature Setup Guide: ${feature}
3471
3728
 
3472
3729
  ${FEATURES[feature] || "Unknown feature."}
@@ -3489,7 +3746,8 @@ function handler6() {
3489
3746
  ## Server Info
3490
3747
  - **Name**: 01.software MCP Server
3491
3748
  - **Version**: 0.1.0
3492
- - **Transport**: HTTP (Streamable)
3749
+ - **Hosted transport**: HTTP (Streamable)
3750
+ - **Local transport**: stdio through \`npx @01.software/cli mcp\`
3493
3751
 
3494
3752
  ## Authentication
3495
3753
 
@@ -3500,42 +3758,9 @@ HTTP MCP uses OAuth discovery and Authorization Code + PKCE.
3500
3758
  url = "https://mcp.01.software/mcp"
3501
3759
  \`\`\`
3502
3760
 
3503
- ## Available Tools (29)
3504
-
3505
- > Generic write tools (create/update/delete/update-many/delete-many) are intentionally absent. Use the dedicated workflow tools below or the SDK (\`client.collections.from(slug).create()\` / \`update()\` / \`remove()\` / \`updateMany()\` / \`removeMany()\`) for stateful mutations.
3506
-
3507
- ### Generic Read (2)
3508
- - \`query-collection\` - Query collection with filters, pagination, sorting
3509
- - \`get-collection-by-id\` - Get single item by ID
3761
+ ## Hosted HTTP OAuth Tools (8)
3510
3762
 
3511
- ### Orders (7)
3512
- - \`create-order\` - Create a new order with products and shipping
3513
- - \`get-order\` - Get order details by order number
3514
- - \`update-order\` - Update order status
3515
- - \`checkout\` - Convert cart to order
3516
- - \`create-fulfillment\` - Create fulfillment for order items
3517
- - \`update-fulfillment\` - Update fulfillment status, carrier, and tracking
3518
- - \`update-transaction\` - Update transaction status
3519
-
3520
- ### Returns (3)
3521
- - \`create-return\` - Create a return request
3522
- - \`update-return\` - Update return status
3523
- - \`return-with-refund\` - Process return with refund atomically
3524
-
3525
- ### Cart (6)
3526
- - \`add-cart-item\` - Add item to cart
3527
- - \`update-cart-item\` - Update cart item quantity
3528
- - \`remove-cart-item\` - Remove item from cart
3529
- - \`apply-discount\` - Apply discount code to cart
3530
- - \`remove-discount\` - Remove discount from cart
3531
- - \`clear-cart\` - Remove all items from cart
3532
-
3533
- ### Validation (2)
3534
- - \`validate-discount\` - Validate discount code
3535
- - \`calculate-shipping\` - Calculate shipping fee
3536
-
3537
- ### Product (1)
3538
- - \`stock-check\` - Check product option stock availability
3763
+ The hosted HTTP MCP endpoint at https://mcp.01.software/mcp exposes only these OAuth-safe tools:
3539
3764
 
3540
3765
  ### Schema (1)
3541
3766
  - \`get-collection-schema\` - Get authoritative tenant-aware collection schema
@@ -3553,6 +3778,16 @@ url = "https://mcp.01.software/mcp"
3553
3778
  - \`sdk-get-auth-setup\` - Get framework-specific auth setup guidance
3554
3779
  - \`sdk-get-collection-pattern\` - Get collection-specific usage patterns
3555
3780
 
3781
+ ## Local CLI Stdio Surface (29)
3782
+
3783
+ For trusted local server-key workflows, start the stdio server:
3784
+
3785
+ \`\`\`bash
3786
+ npx @01.software/cli mcp
3787
+ \`\`\`
3788
+
3789
+ Local stdio can expose generic read, order, return, cart, validation, stock, schema, tenant context, field config, and guidance tools. Generic collection write tools (create/update/delete/update-many/delete-many) are intentionally absent on every transport; use the SDK server client for generic writes.
3790
+
3556
3791
  ## Rate Limits
3557
3792
 
3558
3793
  Rate limits depend on your tenant plan:
@@ -3564,7 +3799,7 @@ Rate limits depend on your tenant plan:
3564
3799
  }
3565
3800
 
3566
3801
  // src/resources/(collections)/schema.ts
3567
- import { COLLECTIONS as COLLECTIONS6 } from "@01.software/sdk";
3802
+ import { COLLECTIONS as COLLECTIONS3 } from "@01.software/sdk";
3568
3803
  var metadata35 = {
3569
3804
  name: "collections-schema",
3570
3805
  title: "Collection Schema Info",
@@ -3593,13 +3828,18 @@ var COLLECTIONS_BY_CATEGORY = {
3593
3828
  Customers: [
3594
3829
  "customers",
3595
3830
  "customer-profiles",
3596
- "customer-addresses",
3597
- "customer-groups"
3831
+ "customer-profile-lists",
3832
+ "customer-addresses"
3598
3833
  ],
3599
3834
  Carts: ["carts", "cart-items"],
3600
3835
  "Discounts & Promotions": ["discounts", "promotions"],
3601
3836
  Documents: ["documents", "document-categories", "document-types"],
3602
- Articles: ["articles", "article-authors", "article-categories", "article-tags"],
3837
+ Articles: [
3838
+ "articles",
3839
+ "article-authors",
3840
+ "article-categories",
3841
+ "article-tags"
3842
+ ],
3603
3843
  Community: [
3604
3844
  "posts",
3605
3845
  "comments",
@@ -3618,7 +3858,12 @@ var COLLECTIONS_BY_CATEGORY = {
3618
3858
  "track-categories",
3619
3859
  "track-tags"
3620
3860
  ],
3621
- Galleries: ["galleries", "gallery-items", "gallery-categories", "gallery-tags"],
3861
+ Galleries: [
3862
+ "galleries",
3863
+ "gallery-items",
3864
+ "gallery-categories",
3865
+ "gallery-tags"
3866
+ ],
3622
3867
  Links: ["links", "link-categories", "link-tags"],
3623
3868
  Canvas: [
3624
3869
  "canvases",
@@ -3643,7 +3888,7 @@ var COLLECTIONS_BY_CATEGORY = {
3643
3888
  };
3644
3889
  function handler7() {
3645
3890
  const categoryDocs = Object.entries(COLLECTIONS_BY_CATEGORY).map(([category, collections]) => {
3646
- const collectionList = collections.filter((c) => COLLECTIONS6.includes(c)).map((c) => `- **${c}**`).join("\n");
3891
+ const collectionList = collections.filter((c) => COLLECTIONS3.includes(c)).map((c) => `- **${c}**`).join("\n");
3647
3892
  return `## ${category}
3648
3893
  ${collectionList}`;
3649
3894
  }).join("\n\n");
@@ -3664,8 +3909,9 @@ Each collection supports the following operations:
3664
3909
  - \`updateMany(where, data)\` - Bulk update items matching filter
3665
3910
  - \`removeMany(where)\` - Bulk delete items matching filter
3666
3911
 
3667
- Draft-enabled public collections expose only \`_status: 'published'\` rows to
3668
- publishable-key reads unless server-side access explicitly includes drafts.
3912
+ Status-managed public collections expose only \`status: 'published'\` rows to
3913
+ publishable-key reads unless server-side access explicitly includes
3914
+ unpublished statuses.
3669
3915
 
3670
3916
  ## Query Examples
3671
3917
 
@@ -3688,7 +3934,7 @@ publishable-key reads unless server-side access explicitly includes drafts.
3688
3934
  }
3689
3935
  \`\`\`
3690
3936
 
3691
- Total available collections: ${COLLECTIONS6.length}`;
3937
+ Total available collections: ${COLLECTIONS3.length}`;
3692
3938
  }
3693
3939
 
3694
3940
  // src/resources/(docs)/getting-started.ts
@@ -5576,8 +5822,25 @@ function registerTool(server, schema34, meta, handler19) {
5576
5822
  };
5577
5823
  }
5578
5824
  }
5579
- const result = await handler19(params);
5580
- return { content: [{ type: "text", text: result }] };
5825
+ const activeSummary = currentMcpTelemetrySummary();
5826
+ const ownSummary = activeSummary || hasRequestContext() ? null : createMcpTelemetrySummary("stdio");
5827
+ const summary = activeSummary ?? ownSummary;
5828
+ let result = null;
5829
+ try {
5830
+ result = await handler19(params);
5831
+ return { content: [{ type: "text", text: result }] };
5832
+ } finally {
5833
+ if (summary) {
5834
+ recordMcpToolResult({
5835
+ resultText: result,
5836
+ summary,
5837
+ toolName: meta.name
5838
+ });
5839
+ }
5840
+ if (ownSummary) {
5841
+ void flushMcpTelemetrySummary(ownSummary);
5842
+ }
5843
+ }
5581
5844
  }
5582
5845
  );
5583
5846
  }
@@ -5970,9 +6233,9 @@ Resources (12): config, collections-schema, getting-started, guides, api, query-
5970
6233
  Links
5971
6234
  -----
5972
6235
 
5973
- Docs https://01.software/docs/integrations/mcp
5974
- SDK https://01.software/docs/sdk/client
5975
- API Reference https://01.software/docs/api/rest-api
6236
+ Docs https://docs.01.software/docs/developers/integrations/mcp
6237
+ SDK https://docs.01.software/docs/developers/sdk/client
6238
+ API Reference https://docs.01.software/docs/developers/api/rest-api
5976
6239
  Console https://console.01.software
5977
6240
  `;
5978
6241
  var PROTECTED_RESOURCE_METADATA = JSON.stringify({
@@ -5980,14 +6243,23 @@ var PROTECTED_RESOURCE_METADATA = JSON.stringify({
5980
6243
  authorization_servers: [MCP_OAUTH_ISSUER],
5981
6244
  scopes_supported: [MCP_SCOPES.read, MCP_SCOPES.write]
5982
6245
  });
6246
+ var PROTECTED_RESOURCE_METADATA_URL = new URL(
6247
+ MCP_PROTECTED_RESOURCE_METADATA_PATH,
6248
+ MCP_RESOURCE_AUDIENCE
6249
+ ).toString();
5983
6250
  var SERVICE_JWKS_PATH = "/.well-known/service-jwks.json";
5984
6251
  function writeOAuthError(res, status, error, description) {
5985
6252
  res.writeHead(status, {
5986
6253
  "Content-Type": "application/json",
5987
- "WWW-Authenticate": `Bearer resource_metadata="${MCP_PROTECTED_RESOURCE_METADATA_PATH}", error="${error}", error_description="${description}"`
6254
+ "WWW-Authenticate": `Bearer resource_metadata="${PROTECTED_RESOURCE_METADATA_URL}", error="${error}", error_description="${description}"`
5988
6255
  });
5989
6256
  res.end(JSON.stringify({ error, error_description: description }));
5990
6257
  }
6258
+ function acceptsEventStream(req) {
6259
+ const accept = getHeaderValue(req.headers, "accept");
6260
+ if (!accept) return false;
6261
+ return accept.split(",").some((entry) => entry.trim().split(";")[0]?.toLowerCase() === "text/event-stream");
6262
+ }
5991
6263
  async function handler18(req, res) {
5992
6264
  setCors(res);
5993
6265
  if (req.method === "OPTIONS") {
@@ -6020,6 +6292,11 @@ async function handler18(req, res) {
6020
6292
  }
6021
6293
  return;
6022
6294
  }
6295
+ if (acceptsEventStream(req)) {
6296
+ res.writeHead(405, { "Content-Type": "application/json" });
6297
+ res.end(METHOD_NOT_ALLOWED);
6298
+ return;
6299
+ }
6023
6300
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
6024
6301
  res.end(HOME_PAGE);
6025
6302
  return;
@@ -6056,14 +6333,19 @@ async function handler18(req, res) {
6056
6333
  if (typeof value === "string") headers.set(key, value);
6057
6334
  else if (Array.isArray(value)) headers.set(key, value.join(", "));
6058
6335
  }
6336
+ const telemetrySummary = createMcpTelemetrySummary("http");
6059
6337
  await requestContext.run({ headers, auth: auth.context }, async () => {
6060
6338
  try {
6061
- const body = req.body ?? JSON.parse(await readBody(req));
6062
- await transport.handleRequest(req, res, body);
6339
+ await runWithMcpTelemetry(telemetrySummary, async () => {
6340
+ const body = req.body ?? JSON.parse(await readBody(req));
6341
+ await transport.handleRequest(req, res, body);
6342
+ });
6063
6343
  } catch (err) {
6064
6344
  writeRequestError(res, err);
6065
6345
  } finally {
6346
+ telemetrySummary.durationMs = Date.now() - telemetrySummary.startedAtMs;
6066
6347
  await close();
6348
+ await flushMcpTelemetrySummary(telemetrySummary);
6067
6349
  }
6068
6350
  });
6069
6351
  }