@sonicjs-cms/core 3.0.0-beta.23 → 3.0.0-beta.24

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.
Files changed (57) hide show
  1. package/dist/{chunk-IS54FPB7.cjs → chunk-37V3RET3.cjs} +18 -34
  2. package/dist/chunk-37V3RET3.cjs.map +1 -0
  3. package/dist/{chunk-VGHEKIJL.cjs → chunk-A2SN3RVL.cjs} +2 -2
  4. package/dist/{chunk-VGHEKIJL.cjs.map → chunk-A2SN3RVL.cjs.map} +1 -1
  5. package/dist/{chunk-4WMZGHIC.cjs → chunk-BP52BFIV.cjs} +4 -4
  6. package/dist/{chunk-4WMZGHIC.cjs.map → chunk-BP52BFIV.cjs.map} +1 -1
  7. package/dist/{chunk-DIBCKCUE.js → chunk-FHPEM77Y.js} +8 -24
  8. package/dist/chunk-FHPEM77Y.js.map +1 -0
  9. package/dist/{chunk-CTYLCWMO.cjs → chunk-I767LVN7.cjs} +252 -217
  10. package/dist/chunk-I767LVN7.cjs.map +1 -0
  11. package/dist/{chunk-C3ZQP5NW.cjs → chunk-KKNEYIZJ.cjs} +3 -3
  12. package/dist/{chunk-C3ZQP5NW.cjs.map → chunk-KKNEYIZJ.cjs.map} +1 -1
  13. package/dist/{chunk-DPLJYTHE.js → chunk-LNI3FYSI.js} +3 -3
  14. package/dist/{chunk-DPLJYTHE.js.map → chunk-LNI3FYSI.js.map} +1 -1
  15. package/dist/{chunk-AT4ZQGEZ.cjs → chunk-QCJHCSOK.cjs} +21 -2
  16. package/dist/chunk-QCJHCSOK.cjs.map +1 -0
  17. package/dist/{chunk-RFBW5RRH.cjs → chunk-QJUJNTIU.cjs} +11 -11
  18. package/dist/{chunk-RFBW5RRH.cjs.map → chunk-QJUJNTIU.cjs.map} +1 -1
  19. package/dist/{chunk-KS24NPPL.js → chunk-RJ2O5IIE.js} +21 -2
  20. package/dist/chunk-RJ2O5IIE.js.map +1 -0
  21. package/dist/{chunk-A46IRC5A.js → chunk-UIHIXTIS.js} +2 -2
  22. package/dist/{chunk-A46IRC5A.js.map → chunk-UIHIXTIS.js.map} +1 -1
  23. package/dist/{chunk-SK5CHFJP.js → chunk-VC5XVPLM.js} +52 -20
  24. package/dist/chunk-VC5XVPLM.js.map +1 -0
  25. package/dist/{chunk-2MIYYAO4.js → chunk-VGEZNFWH.js} +3 -3
  26. package/dist/{chunk-2MIYYAO4.js.map → chunk-VGEZNFWH.js.map} +1 -1
  27. package/dist/{chunk-XXRR54AB.js → chunk-VYQVDL2M.js} +4 -4
  28. package/dist/{chunk-XXRR54AB.js.map → chunk-VYQVDL2M.js.map} +1 -1
  29. package/dist/index.cjs +1093 -264
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +28 -1
  32. package/dist/index.d.ts +28 -1
  33. package/dist/index.js +841 -14
  34. package/dist/index.js.map +1 -1
  35. package/dist/middleware.cjs +33 -33
  36. package/dist/middleware.js +4 -4
  37. package/dist/migrations-FGXZPKDC.cjs +13 -0
  38. package/dist/{migrations-ZD7Y5NUC.cjs.map → migrations-FGXZPKDC.cjs.map} +1 -1
  39. package/dist/migrations-Z6D27NAW.js +4 -0
  40. package/dist/{migrations-XQ6EX6HX.js.map → migrations-Z6D27NAW.js.map} +1 -1
  41. package/dist/plugins.cjs +40 -40
  42. package/dist/plugins.js +3 -3
  43. package/dist/routes.cjs +26 -26
  44. package/dist/routes.js +6 -6
  45. package/dist/services.cjs +18 -18
  46. package/dist/services.js +2 -2
  47. package/dist/utils.cjs +3 -3
  48. package/dist/utils.js +1 -1
  49. package/package.json +1 -1
  50. package/dist/chunk-AT4ZQGEZ.cjs.map +0 -1
  51. package/dist/chunk-CTYLCWMO.cjs.map +0 -1
  52. package/dist/chunk-DIBCKCUE.js.map +0 -1
  53. package/dist/chunk-IS54FPB7.cjs.map +0 -1
  54. package/dist/chunk-KS24NPPL.js.map +0 -1
  55. package/dist/chunk-SK5CHFJP.js.map +0 -1
  56. package/dist/migrations-XQ6EX6HX.js +0 -4
  57. package/dist/migrations-ZD7Y5NUC.cjs +0 -13
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createAuth } from './chunk-SOZUG52O.js';
2
- import { getEmailService, hasEmailService, setEmailService, reconcileMenuFromPlugins, invalidateVariablesCache, resolveVariablesInObject, resolveDocScope, denyIfNotAllowed, DocumentRepository, dispatchHookEvent, getCustomData, createDocumentSchema, renderTable, getVariablesCached, setVariablesCache, TenantService, isValidMemberRole, renderConfirmationDialog, getConfirmationDialogScript, VALID_MEMBER_ROLES, userProfilesPlugin, lexicalEditorPlugin, api_media_default, api_system_default, apiDocumentsRoutes, api_default, adminDocumentsRoutes, admin_api_default, adminCollectionsRoutes, adminSettingsRoutes, router2, admin_content_default, adminMediaRoutes, router, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-SK5CHFJP.js';
3
- export { DocumentRepository, ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes, createUserProfilesPlugin, defineUserProfile, getUserProfileConfig, userProfilesPlugin } from './chunk-SK5CHFJP.js';
2
+ import { getEmailService, hasEmailService, setEmailService, reconcileMenuFromPlugins, invalidateVariablesCache, resolveVariablesInObject, resolveDocScope, denyIfNotAllowed, DocumentRepository, dispatchHookEvent, getCustomData, createDocumentSchema, renderTable, getVariablesCached, setVariablesCache, TenantService, isValidMemberRole, renderConfirmationDialog, getConfirmationDialogScript, VALID_MEMBER_ROLES, userProfilesPlugin, lexicalEditorPlugin, api_media_default, api_system_default, apiDocumentsRoutes, api_default, adminDocumentsRoutes, admin_api_default, adminCollectionsRoutes, adminSettingsRoutes, router2, admin_content_default, adminMediaRoutes, router, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default, getDocumentRequestContext, updateDocumentSchema, effectiveTenantForType } from './chunk-VC5XVPLM.js';
3
+ export { DocumentRepository, ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes, createUserProfilesPlugin, defineUserProfile, getUserProfileConfig, userProfilesPlugin } from './chunk-VC5XVPLM.js';
4
4
  import './chunk-VVYVFCML.js';
5
5
  import './chunk-BHND5PEV.js';
6
6
  import { SettingsService, setAppInstance } from './chunk-2XAQTPAE.js';
@@ -8,30 +8,30 @@ export { Logger, getLogger, initLogger } from './chunk-2XAQTPAE.js';
8
8
  import { getAllCacheStats, CACHE_CONFIGS, getCacheService, clearAllCaches, parseCacheKey } from './chunk-K25XHMM3.js';
9
9
  import { schema_exports } from './chunk-AI663NBO.js';
10
10
  export { apiTokens, content, contentVersions, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-AI663NBO.js';
11
- import { requireAuth, DocumentTypeRegistry, requireRbac, requireRole, getJwtExpirySecondsFromDb, AuthManager, metricsMiddleware, bootstrapMiddleware, securityHeadersMiddleware, csrfProtection } from './chunk-DIBCKCUE.js';
12
- export { AuthManager, PermissionManager, bootstrapDocumentTypes, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeadersMiddleware as securityHeaders, securityLoggingMiddleware } from './chunk-DIBCKCUE.js';
11
+ import { requireAuth, DocumentTypeRegistry, requireRbac, requireRole, getJwtExpirySecondsFromDb, AuthManager, metricsMiddleware, bootstrapMiddleware, securityHeadersMiddleware, csrfProtection } from './chunk-FHPEM77Y.js';
12
+ export { AuthManager, PermissionManager, bootstrapDocumentTypes, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeadersMiddleware as securityHeaders, securityLoggingMiddleware } from './chunk-FHPEM77Y.js';
13
13
  import { RbacService, DocumentsService } from './chunk-3TMTYJAJ.js';
14
14
  export { DocumentsService, RbacService } from './chunk-3TMTYJAJ.js';
15
15
  import { nanoid } from './chunk-UWUP2VMT.js';
16
- import { PluginService, PLUGIN_REGISTRY, invalidateTenantCache, getCollectionRegistry, TENANT_COOKIE, MULTI_TENANT_PLUGIN_ID, collectionRecordToRow, tenantMiddleware } from './chunk-KS24NPPL.js';
17
- export { PluginBootstrapService, PluginService as PluginServiceClass, getAvailableCollectionNames, loadCollectionConfig, loadCollectionConfigs, registerCollections, validateCollectionConfig } from './chunk-KS24NPPL.js';
18
- export { MigrationService } from './chunk-A46IRC5A.js';
16
+ import { PluginService, PLUGIN_REGISTRY, invalidateTenantCache, getCollectionRegistry, TENANT_COOKIE, MULTI_TENANT_PLUGIN_ID, collectionRecordToRow, tenantMiddleware } from './chunk-RJ2O5IIE.js';
17
+ export { PluginBootstrapService, PluginService as PluginServiceClass, getAvailableCollectionNames, loadCollectionConfig, loadCollectionConfigs, registerCollections, validateCollectionConfig } from './chunk-RJ2O5IIE.js';
18
+ export { MigrationService } from './chunk-UIHIXTIS.js';
19
19
  export { renderFilterBar } from './chunk-URIBTX27.js';
20
20
  import { renderAdminLayout } from './chunk-BJROXF4N.js';
21
21
  export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-BJROXF4N.js';
22
22
  import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-THFZSDIU.js';
23
23
  export { renderAdminLayoutCatalyst } from './chunk-THFZSDIU.js';
24
- import { TurnstileService, HookSystemImpl, createPluginWirer, registerPluginRoutes } from './chunk-XXRR54AB.js';
25
- export { HookSystemImpl, HookUtils, PluginDependencyCycleError, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, RegisterPluginsError, ScopedHookSystem as ScopedHookSystemClass, collectCronSchedules, collectCrons, createScheduledHandler, dispatchCronTick, registerPlugins, topoSort } from './chunk-XXRR54AB.js';
26
- import { definePlugin, setPluginMenu, setPluginDefinitions, resolvePluginMenuItems } from './chunk-2MIYYAO4.js';
27
- export { applySchemaDefaults, definePlugin, getPluginMenu, isDefinedPlugin, parseConfigSchema, parseFormDataToSettings, renderSchemaFields, resetPluginMenu, resolvePluginMenuItems, setPluginMenu } from './chunk-2MIYYAO4.js';
24
+ import { TurnstileService, HookSystemImpl, createPluginWirer, registerPluginRoutes } from './chunk-VYQVDL2M.js';
25
+ export { HookSystemImpl, HookUtils, PluginDependencyCycleError, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, RegisterPluginsError, ScopedHookSystem as ScopedHookSystemClass, collectCronSchedules, collectCrons, createScheduledHandler, dispatchCronTick, registerPlugins, topoSort } from './chunk-VYQVDL2M.js';
26
+ import { definePlugin, setPluginMenu, setPluginDefinitions, resolvePluginMenuItems } from './chunk-VGEZNFWH.js';
27
+ export { applySchemaDefaults, definePlugin, getPluginMenu, isDefinedPlugin, parseConfigSchema, parseFormDataToSettings, renderSchemaFields, resetPluginMenu, resolvePluginMenuItems, setPluginMenu } from './chunk-VGEZNFWH.js';
28
28
  import { setHookSystem } from './chunk-RNZFGN4R.js';
29
29
  export { getHookSystem, getTypedHooks, hasHookSystem, resetHookSystem, setHookSystem } from './chunk-RNZFGN4R.js';
30
30
  export { QueryFilterBuilder, TemplateRenderer, buildQuery, renderTemplate, templateRenderer } from './chunk-QZGABF2M.js';
31
31
  export { metricsTracker } from './chunk-FICTAGD4.js';
32
32
  import './chunk-X7ZAEI5S.js';
33
- import { getCoreVersion, package_default } from './chunk-DPLJYTHE.js';
34
- export { SONICJS_VERSION, getCoreVersion } from './chunk-DPLJYTHE.js';
33
+ import { getCoreVersion, package_default } from './chunk-LNI3FYSI.js';
34
+ export { SONICJS_VERSION, getCoreVersion } from './chunk-LNI3FYSI.js';
35
35
  import { escapeHtml, sanitizeInput } from './chunk-TQABQWOP.js';
36
36
  export { escapeHtml, sanitizeInput, sanitizeObject } from './chunk-TQABQWOP.js';
37
37
  export { HOOKS } from './chunk-QXOZI5Q2.js';
@@ -22051,10 +22051,837 @@ var site_settings_collection_default = {
22051
22051
  function createDb(d1) {
22052
22052
  return drizzle(d1, { schema: schema_exports });
22053
22053
  }
22054
+ var mcpTypeConfigSchema = z.object({
22055
+ read: z.boolean().default(true),
22056
+ write: z.boolean().default(true)
22057
+ });
22058
+ var mcpConfigSchema = z.object({
22059
+ /** Collections exposed over MCP. Omitted → every active collection. */
22060
+ expose: z.array(z.string()).optional(),
22061
+ /** Per-collection read/write overrides, keyed by collection name. */
22062
+ types: z.record(z.string(), mcpTypeConfigSchema).default({}),
22063
+ /** Field names stripped from every tool response + write schema. */
22064
+ redactFields: z.array(z.string()).default([]),
22065
+ /** Max documents returned by list_* tools and document resources. */
22066
+ listLimit: z.number().int().positive().max(200).default(50)
22067
+ });
22068
+ function resolveMcpConfig(raw, collections) {
22069
+ const cfg = mcpConfigSchema.parse(raw ?? {});
22070
+ const exposeSet = cfg.expose ? new Set(cfg.expose) : null;
22071
+ const types = collections.filter((c) => exposeSet ? exposeSet.has(c.name) : true).map((c) => {
22072
+ const override = cfg.types[c.name];
22073
+ return {
22074
+ typeId: c.name,
22075
+ displayName: c.displayName ?? c.name,
22076
+ read: override?.read ?? true,
22077
+ write: override?.write ?? true
22078
+ };
22079
+ });
22080
+ return { types, redactFields: cfg.redactFields, listLimit: cfg.listLimit };
22081
+ }
22082
+
22083
+ // src/plugins/core-plugins/mcp-plugin/schema/field-to-jsonschema.ts
22084
+ var SYSTEM_FIELDS = /* @__PURE__ */ new Set([
22085
+ "id",
22086
+ "root_id",
22087
+ "rootId",
22088
+ "created_at",
22089
+ "createdAt",
22090
+ "updated_at",
22091
+ "updatedAt",
22092
+ "version_number",
22093
+ "versionNumber"
22094
+ ]);
22095
+ function fieldToJsonSchema(field) {
22096
+ const base = {};
22097
+ if (field.title) base.title = field.title;
22098
+ if (field.description) base.description = field.description;
22099
+ switch (field.type) {
22100
+ case "number":
22101
+ return {
22102
+ ...base,
22103
+ type: "number",
22104
+ ...field.min != null ? { minimum: field.min } : {},
22105
+ ...field.max != null ? { maximum: field.max } : {}
22106
+ };
22107
+ case "boolean":
22108
+ case "checkbox":
22109
+ return { ...base, type: "boolean" };
22110
+ case "date":
22111
+ case "datetime":
22112
+ return { ...base, type: "string", format: "date-time" };
22113
+ case "select":
22114
+ case "radio":
22115
+ return { ...base, type: "string", ...field.enum ? { enum: field.enum } : {} };
22116
+ case "multiselect":
22117
+ return {
22118
+ ...base,
22119
+ type: "array",
22120
+ items: { type: "string", ...field.enum ? { enum: field.enum } : {} }
22121
+ };
22122
+ case "array":
22123
+ return {
22124
+ ...base,
22125
+ type: "array",
22126
+ items: field.items ? fieldToJsonSchema(field.items) : {}
22127
+ };
22128
+ case "object":
22129
+ return {
22130
+ ...base,
22131
+ type: "object",
22132
+ ...field.properties ? { properties: mapProperties(field.properties) } : {}
22133
+ };
22134
+ case "json":
22135
+ return { ...base };
22136
+ // string-like: string, slug, email, url, richtext, markdown, textarea, editors,
22137
+ // reference, media, color, file, user
22138
+ default: {
22139
+ const node = { ...base, type: "string" };
22140
+ if (field.minLength != null) node.minLength = field.minLength;
22141
+ if (field.maxLength != null) node.maxLength = field.maxLength;
22142
+ if (field.pattern) node.pattern = field.pattern;
22143
+ if (field.format) node.format = field.format;
22144
+ return node;
22145
+ }
22146
+ }
22147
+ }
22148
+ function mapProperties(props) {
22149
+ const out = {};
22150
+ for (const [name, field] of Object.entries(props)) {
22151
+ out[name] = fieldToJsonSchema(field);
22152
+ }
22153
+ return out;
22154
+ }
22155
+ function collectionToJsonSchema(schema, opts = {}) {
22156
+ const properties = {};
22157
+ const required = [];
22158
+ const redact2 = new Set(opts.redactFields ?? []);
22159
+ const schemaRequired = new Set(schema.required ?? []);
22160
+ for (const [name, field] of Object.entries(schema.properties ?? {})) {
22161
+ if (redact2.has(name)) continue;
22162
+ if (opts.forWrite && SYSTEM_FIELDS.has(name)) continue;
22163
+ properties[name] = fieldToJsonSchema(field);
22164
+ if (field.required || schemaRequired.has(name)) required.push(name);
22165
+ }
22166
+ return { type: "object", properties, ...required.length ? { required } : {} };
22167
+ }
22168
+
22169
+ // src/plugins/core-plugins/mcp-plugin/tools/registry.ts
22170
+ var PHASE_FLAGS = { includeWrite: true, includeSearch: false };
22171
+ var EMPTY_OBJECT_SCHEMA = { type: "object", properties: {} };
22172
+ function buildToolRegistry(cfg, collections, opts = {}) {
22173
+ const tools = [];
22174
+ tools.push({
22175
+ name: "list_collections",
22176
+ description: "List every content collection exposed over MCP, with its read/write access.",
22177
+ inputSchema: EMPTY_OBJECT_SCHEMA,
22178
+ op: "list_collections"
22179
+ });
22180
+ if (opts.includeSearch) {
22181
+ tools.push({
22182
+ name: "search_content",
22183
+ description: "Search content across exposed collections by title or slug.",
22184
+ inputSchema: {
22185
+ type: "object",
22186
+ properties: { query: { type: "string", description: "Text matched against document title and slug." } },
22187
+ required: ["query"]
22188
+ },
22189
+ op: "search_content"
22190
+ });
22191
+ }
22192
+ for (const t of cfg.types) {
22193
+ const col = collections.get(t.typeId);
22194
+ const writeSchema = col ? collectionToJsonSchema(col.schema, { forWrite: true, redactFields: cfg.redactFields }) : EMPTY_OBJECT_SCHEMA;
22195
+ if (t.read) {
22196
+ tools.push({
22197
+ name: `list_${t.typeId}`,
22198
+ description: `List ${t.displayName} documents.`,
22199
+ inputSchema: {
22200
+ type: "object",
22201
+ properties: {
22202
+ status: {
22203
+ type: "string",
22204
+ enum: ["published", "draft"],
22205
+ description: "Lifecycle to list. Defaults to published."
22206
+ },
22207
+ limit: { type: "number", description: `Max documents to return (capped at ${cfg.listLimit}).` }
22208
+ }
22209
+ },
22210
+ op: "list",
22211
+ typeId: t.typeId
22212
+ });
22213
+ tools.push({
22214
+ name: `get_${t.typeId}`,
22215
+ description: `Get one ${t.displayName} document by id or slug.`,
22216
+ inputSchema: {
22217
+ type: "object",
22218
+ properties: {
22219
+ id: { type: "string", description: "Document id or root id." },
22220
+ slug: { type: "string", description: "Document slug (alternative to id)." }
22221
+ }
22222
+ },
22223
+ op: "get",
22224
+ typeId: t.typeId
22225
+ });
22226
+ }
22227
+ if (opts.includeWrite && t.write) {
22228
+ tools.push({
22229
+ name: `create_${t.typeId}`,
22230
+ description: `Create a ${t.displayName} document.`,
22231
+ inputSchema: {
22232
+ type: "object",
22233
+ properties: {
22234
+ title: { type: "string" },
22235
+ slug: { type: "string" },
22236
+ data: writeSchema,
22237
+ publish: { type: "boolean", description: "Publish immediately. Defaults to false (draft)." }
22238
+ }
22239
+ },
22240
+ op: "create",
22241
+ typeId: t.typeId
22242
+ });
22243
+ tools.push({
22244
+ name: `update_${t.typeId}`,
22245
+ description: `Update a ${t.displayName} document (saves a new draft).`,
22246
+ inputSchema: {
22247
+ type: "object",
22248
+ properties: {
22249
+ id: { type: "string", description: "Root id of the document to update." },
22250
+ title: { type: "string" },
22251
+ slug: { type: "string" },
22252
+ data: writeSchema
22253
+ },
22254
+ required: ["id"]
22255
+ },
22256
+ op: "update",
22257
+ typeId: t.typeId
22258
+ });
22259
+ tools.push({
22260
+ name: `publish_${t.typeId}`,
22261
+ description: `Publish a ${t.displayName} document.`,
22262
+ inputSchema: {
22263
+ type: "object",
22264
+ properties: { id: { type: "string", description: "Root id of the document to publish." } },
22265
+ required: ["id"]
22266
+ },
22267
+ op: "publish",
22268
+ typeId: t.typeId
22269
+ });
22270
+ tools.push({
22271
+ name: `delete_${t.typeId}`,
22272
+ description: `Delete a ${t.displayName} document.`,
22273
+ inputSchema: {
22274
+ type: "object",
22275
+ properties: { id: { type: "string", description: "Root id of the document to delete." } },
22276
+ required: ["id"]
22277
+ },
22278
+ op: "delete",
22279
+ typeId: t.typeId
22280
+ });
22281
+ }
22282
+ }
22283
+ return tools;
22284
+ }
22285
+
22286
+ // src/plugins/core-plugins/mcp-plugin/jsonrpc.ts
22287
+ var JSON_RPC = {
22288
+ PARSE_ERROR: -32700,
22289
+ INVALID_REQUEST: -32600,
22290
+ METHOD_NOT_FOUND: -32601,
22291
+ INVALID_PARAMS: -32602,
22292
+ INTERNAL_ERROR: -32603,
22293
+ /** Custom: no valid API key on the request. */
22294
+ UNAUTHORIZED: -32001
22295
+ };
22296
+ var McpToolError = class extends Error {
22297
+ constructor(message) {
22298
+ super(message);
22299
+ this.name = "McpToolError";
22300
+ }
22301
+ };
22302
+ function rpcResult(id, result) {
22303
+ return { jsonrpc: "2.0", id: id ?? null, result };
22304
+ }
22305
+ function rpcError(id, code, message, data) {
22306
+ return { jsonrpc: "2.0", id: id ?? null, error: { code, message, ...{} } };
22307
+ }
22308
+ function parseJsonRpc(body) {
22309
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
22310
+ return { error: { code: JSON_RPC.INVALID_REQUEST, message: "Request must be a JSON object" } };
22311
+ }
22312
+ const b = body;
22313
+ if (b.jsonrpc !== "2.0") {
22314
+ return { error: { code: JSON_RPC.INVALID_REQUEST, message: 'jsonrpc must be "2.0"' } };
22315
+ }
22316
+ if (typeof b.method !== "string" || b.method.length === 0) {
22317
+ return { error: { code: JSON_RPC.INVALID_REQUEST, message: "method must be a non-empty string" } };
22318
+ }
22319
+ const id = b.id === void 0 ? null : b.id;
22320
+ const params = b.params && typeof b.params === "object" && !Array.isArray(b.params) ? b.params : {};
22321
+ return { jsonrpc: "2.0", id, method: b.method, params };
22322
+ }
22323
+
22324
+ // src/plugins/core-plugins/mcp-plugin/tools/documents.ts
22325
+ function redact(data, fields) {
22326
+ if (!fields.length) return data;
22327
+ const out = { ...data };
22328
+ for (const f of fields) delete out[f];
22329
+ return out;
22330
+ }
22331
+ function shapeDocument(doc, redactFields) {
22332
+ return {
22333
+ id: doc.id,
22334
+ rootId: doc.rootId,
22335
+ typeId: doc.typeId,
22336
+ title: doc.title,
22337
+ slug: doc.slug,
22338
+ status: doc.status,
22339
+ isPublished: doc.isPublished,
22340
+ publishedAt: doc.publishedAt,
22341
+ updatedAt: doc.updatedAt,
22342
+ data: redact(doc.data, redactFields)
22343
+ };
22344
+ }
22345
+ async function resolveType(ctx, typeId) {
22346
+ const docType = await new DocumentTypeRegistry(ctx.db).findById(typeId);
22347
+ if (!docType || !docType.isActive) throw new McpToolError(`Unknown document type: ${typeId}`);
22348
+ const effTenant = effectiveTenantForType(ctx.tenantId, docType.settings);
22349
+ const repo = new DocumentRepository(ctx.db, effTenant);
22350
+ return { docType, repo };
22351
+ }
22352
+ async function execList(ctx, typeId, args) {
22353
+ const { docType, repo } = await resolveType(ctx, typeId);
22354
+ const status = args.status === "draft" ? "draft" : "published";
22355
+ const limit = Math.min(Math.max(1, args.limit ?? ctx.listLimit), ctx.listLimit);
22356
+ const now = Math.floor(Date.now() / 1e3);
22357
+ const docs = await repo.list({
22358
+ typeId,
22359
+ status,
22360
+ timeWindow: status === "published",
22361
+ now,
22362
+ limit
22363
+ });
22364
+ const allowed = await Promise.all(
22365
+ docs.map((d) => repo.isAllowed(ctx.principalSet, d.rootId, "read", docType.settings))
22366
+ );
22367
+ return docs.filter((_, i) => allowed[i]).map((d) => shapeDocument(d, ctx.redactFields));
22368
+ }
22369
+ async function execGet(ctx, typeId, args) {
22370
+ const { docType, repo } = await resolveType(ctx, typeId);
22371
+ let doc = null;
22372
+ if (args.id) {
22373
+ doc = await repo.getById(args.id) ?? await repo.getPublished(args.id) ?? await repo.getCurrentDraft(args.id);
22374
+ } else if (args.slug) {
22375
+ doc = await repo.getBySlug(typeId, args.slug);
22376
+ } else {
22377
+ throw new McpToolError('get requires an "id" or "slug" argument');
22378
+ }
22379
+ if (!doc || doc.typeId !== typeId) throw new McpToolError("Document not found");
22380
+ const ok = await repo.isAllowed(ctx.principalSet, doc.rootId, "read", docType.settings);
22381
+ if (!ok) throw new McpToolError("Document not found");
22382
+ return shapeDocument(doc, ctx.redactFields);
22383
+ }
22384
+
22385
+ // src/plugins/core-plugins/mcp-plugin/tools/mutations.ts
22386
+ async function resolveWriteScope(ctx, typeId) {
22387
+ const docType = await new DocumentTypeRegistry(ctx.db).findById(typeId);
22388
+ if (!docType || !docType.isActive) throw new McpToolError(`Unknown document type: ${typeId}`);
22389
+ const effTenant = effectiveTenantForType(ctx.tenantId, docType.settings);
22390
+ const repo = new DocumentRepository(ctx.db, effTenant);
22391
+ const svc = new DocumentsService(ctx.db, {
22392
+ queryableFields: docType.queryableFields ?? [],
22393
+ typeSchemaVersion: docType.schemaVersion,
22394
+ maxVersionsPerRoot: docType.settings?.maxVersionsPerRoot,
22395
+ tenantId: effTenant,
22396
+ versioning: docType.settings?.versioning ?? false
22397
+ });
22398
+ return { docType, effTenant, repo, svc };
22399
+ }
22400
+ async function assertAllowed(scope, ctx, rootId, permission) {
22401
+ const ok = await scope.repo.isAllowed(ctx.principalSet, rootId, permission, scope.docType.settings ?? {});
22402
+ if (!ok) throw new McpToolError(`Permission denied: cannot ${permission} ${scope.docType.id}`);
22403
+ }
22404
+ function applyUserFieldDefaults(data, typeId, userId) {
22405
+ const col = getCollectionRegistry().getByName(typeId);
22406
+ if (!col?.schema?.properties) return data;
22407
+ const props = col.schema.properties;
22408
+ const patched = { ...data };
22409
+ for (const [name, field] of Object.entries(props)) {
22410
+ if (field.type === "user" && !patched[name]) patched[name] = userId;
22411
+ }
22412
+ return patched;
22413
+ }
22414
+ async function execCreate(ctx, typeId, args) {
22415
+ const scope = await resolveWriteScope(ctx, typeId);
22416
+ await assertAllowed(scope, ctx, "", "create");
22417
+ const data = redact(applyUserFieldDefaults(args.data ?? {}, typeId, ctx.userId), ctx.redactFields);
22418
+ const input = createDocumentSchema.parse({
22419
+ typeId,
22420
+ tenantId: scope.effTenant,
22421
+ title: args.title ?? null,
22422
+ slug: args.slug ?? null,
22423
+ data,
22424
+ ownerId: ctx.userId,
22425
+ publishOnCreate: Boolean(args.publish)
22426
+ });
22427
+ const doc = await scope.svc.create(input, ctx.userId);
22428
+ return shapeDocument(doc, ctx.redactFields);
22429
+ }
22430
+ async function execUpdate(ctx, typeId, args) {
22431
+ if (!args.id) throw new McpToolError('update requires an "id" (root id)');
22432
+ const scope = await resolveWriteScope(ctx, typeId);
22433
+ const current = await scope.repo.getCurrentDraft(args.id);
22434
+ if (!current || current.typeId !== typeId) throw new McpToolError("Document not found");
22435
+ await assertAllowed(scope, ctx, args.id, "update");
22436
+ const patch = updateDocumentSchema.parse({
22437
+ ...args.title !== void 0 ? { title: args.title } : {},
22438
+ ...args.slug !== void 0 ? { slug: args.slug } : {},
22439
+ ...args.data !== void 0 ? { data: redact(args.data, ctx.redactFields) } : {}
22440
+ });
22441
+ const doc = await scope.svc.saveDraft(args.id, patch, ctx.userId);
22442
+ return shapeDocument(doc, ctx.redactFields);
22443
+ }
22444
+ async function execPublish(ctx, typeId, args) {
22445
+ if (!args.id) throw new McpToolError('publish requires an "id" (root id)');
22446
+ const scope = await resolveWriteScope(ctx, typeId);
22447
+ const current = await scope.repo.getCurrentDraft(args.id);
22448
+ if (!current || current.typeId !== typeId) throw new McpToolError("Document not found");
22449
+ await assertAllowed(scope, ctx, args.id, "publish");
22450
+ const doc = await scope.svc.publish(current.id, ctx.userId);
22451
+ return shapeDocument(doc, ctx.redactFields);
22452
+ }
22453
+ async function execDelete(ctx, typeId, args) {
22454
+ if (!args.id) throw new McpToolError('delete requires an "id" (root id)');
22455
+ const scope = await resolveWriteScope(ctx, typeId);
22456
+ const current = await scope.repo.getCurrentDraft(args.id) ?? await scope.repo.getPublished(args.id);
22457
+ if (!current || current.typeId !== typeId) throw new McpToolError("Document not found");
22458
+ await assertAllowed(scope, ctx, args.id, "delete");
22459
+ if (scope.docType.settings?.pii) {
22460
+ await scope.svc.erase(args.id, scope.effTenant);
22461
+ return { rootId: args.id, deleted: true, erased: true };
22462
+ }
22463
+ const ids = /* @__PURE__ */ new Set();
22464
+ ids.add(current.id);
22465
+ if (current.isCurrentDraft) {
22466
+ const published = await scope.repo.getPublished(args.id);
22467
+ if (published) ids.add(published.id);
22468
+ } else {
22469
+ const draft = await scope.repo.getCurrentDraft(args.id);
22470
+ if (draft) ids.add(draft.id);
22471
+ }
22472
+ for (const id of ids) await scope.svc.softDelete(id);
22473
+ return { rootId: args.id, deleted: true, erased: false };
22474
+ }
22475
+
22476
+ // src/plugins/core-plugins/mcp-plugin/tools/static.ts
22477
+ function execListCollections(types) {
22478
+ return types.map((t) => ({ typeId: t.typeId, displayName: t.displayName, read: t.read, write: t.write }));
22479
+ }
22480
+
22481
+ // src/plugins/core-plugins/mcp-plugin/resources/schemas.ts
22482
+ function buildResourceList(cfg) {
22483
+ const out = [
22484
+ { uri: "sonicjs://collections", name: "All collections", mimeType: "application/json" }
22485
+ ];
22486
+ for (const t of cfg.types) {
22487
+ out.push({
22488
+ uri: `sonicjs://collections/${t.typeId}/schema`,
22489
+ name: `${t.displayName} schema`,
22490
+ mimeType: "application/json"
22491
+ });
22492
+ }
22493
+ return out;
22494
+ }
22495
+ var SCHEMA_URI = /^sonicjs:\/\/collections\/([^/]+)\/schema$/;
22496
+ function readResource(uri, cfg, collections) {
22497
+ if (uri === "sonicjs://collections") {
22498
+ const list = cfg.types.map((t) => ({
22499
+ typeId: t.typeId,
22500
+ displayName: t.displayName,
22501
+ read: t.read,
22502
+ write: t.write
22503
+ }));
22504
+ return { uri, mimeType: "application/json", text: JSON.stringify(list, null, 2) };
22505
+ }
22506
+ const m = uri.match(SCHEMA_URI);
22507
+ if (m) {
22508
+ const typeId = m[1];
22509
+ const exposed = cfg.types.find((t) => t.typeId === typeId);
22510
+ const col = collections.get(typeId);
22511
+ if (!exposed || !col) throw new McpToolError(`Unknown collection: ${typeId}`);
22512
+ const schema = collectionToJsonSchema(col.schema, { redactFields: cfg.redactFields });
22513
+ return { uri, mimeType: "application/json", text: JSON.stringify(schema, null, 2) };
22514
+ }
22515
+ throw new McpToolError(`Unknown resource URI: ${uri}`);
22516
+ }
22517
+
22518
+ // src/plugins/core-plugins/mcp-plugin/routes/mcp.ts
22519
+ var SERVER_INFO = { name: "sonicjs-mcp", version: "1.0.0" };
22520
+ var PROTOCOL_VERSION = "2024-11-05";
22521
+ function createMcpRoutes(options = {}) {
22522
+ const app2 = new Hono();
22523
+ app2.post("/", async (c) => {
22524
+ let body;
22525
+ try {
22526
+ body = await c.req.json();
22527
+ } catch {
22528
+ return c.json(rpcError(null, JSON_RPC.PARSE_ERROR, "Invalid JSON"));
22529
+ }
22530
+ const parsed = parseJsonRpc(body);
22531
+ if ("error" in parsed) {
22532
+ const id2 = body && typeof body === "object" ? body.id ?? null : null;
22533
+ return c.json(rpcError(id2, parsed.error.code, parsed.error.message));
22534
+ }
22535
+ const { id, method, params } = parsed;
22536
+ const user = c.get("user");
22537
+ if (!user) {
22538
+ return c.json(
22539
+ rpcError(id, JSON_RPC.UNAUTHORIZED, "Unauthorized: provide a valid API key via Authorization: Bearer sk_...")
22540
+ );
22541
+ }
22542
+ const { tenantId, principalSet, userId } = getDocumentRequestContext(c);
22543
+ const registry = getCollectionRegistry();
22544
+ const active = registry.listActive();
22545
+ const collections = new Map(active.map((col) => [col.name, col]));
22546
+ const cfg = resolveMcpConfig(options, active);
22547
+ const tools = buildToolRegistry(cfg, collections, PHASE_FLAGS);
22548
+ try {
22549
+ switch (method) {
22550
+ case "initialize":
22551
+ return c.json(
22552
+ rpcResult(id, {
22553
+ protocolVersion: PROTOCOL_VERSION,
22554
+ serverInfo: SERVER_INFO,
22555
+ capabilities: { tools: {}, resources: {} }
22556
+ })
22557
+ );
22558
+ case "tools/list":
22559
+ return c.json(
22560
+ rpcResult(id, {
22561
+ tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }))
22562
+ })
22563
+ );
22564
+ case "tools/call": {
22565
+ const name = typeof params.name === "string" ? params.name : "";
22566
+ const args = params.arguments && typeof params.arguments === "object" ? params.arguments : {};
22567
+ const tool = tools.find((t) => t.name === name);
22568
+ if (!tool) return c.json(rpcError(id, JSON_RPC.METHOD_NOT_FOUND, `Unknown tool: ${name}`));
22569
+ const readCtx = {
22570
+ db: c.env.DB,
22571
+ tenantId,
22572
+ principalSet,
22573
+ listLimit: cfg.listLimit,
22574
+ redactFields: cfg.redactFields
22575
+ };
22576
+ const writeCtx = {
22577
+ db: c.env.DB,
22578
+ tenantId,
22579
+ principalSet,
22580
+ userId: userId ?? "",
22581
+ redactFields: cfg.redactFields
22582
+ };
22583
+ try {
22584
+ let result;
22585
+ switch (tool.op) {
22586
+ case "list_collections":
22587
+ result = execListCollections(cfg.types);
22588
+ break;
22589
+ case "list":
22590
+ result = await execList(readCtx, tool.typeId, args);
22591
+ break;
22592
+ case "get":
22593
+ result = await execGet(readCtx, tool.typeId, args);
22594
+ break;
22595
+ case "create":
22596
+ result = await execCreate(writeCtx, tool.typeId, args);
22597
+ break;
22598
+ case "update":
22599
+ result = await execUpdate(writeCtx, tool.typeId, args);
22600
+ break;
22601
+ case "publish":
22602
+ result = await execPublish(writeCtx, tool.typeId, args);
22603
+ break;
22604
+ case "delete":
22605
+ result = await execDelete(writeCtx, tool.typeId, args);
22606
+ break;
22607
+ default:
22608
+ return c.json(rpcError(id, JSON_RPC.METHOD_NOT_FOUND, `Tool not available: ${name}`));
22609
+ }
22610
+ return c.json(rpcResult(id, { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }));
22611
+ } catch (e) {
22612
+ if (e instanceof McpToolError) {
22613
+ return c.json(rpcResult(id, { content: [{ type: "text", text: e.message }], isError: true }));
22614
+ }
22615
+ throw e;
22616
+ }
22617
+ }
22618
+ case "resources/list":
22619
+ return c.json(rpcResult(id, { resources: buildResourceList(cfg) }));
22620
+ case "resources/read": {
22621
+ const uri = typeof params.uri === "string" ? params.uri : "";
22622
+ if (!uri) return c.json(rpcError(id, JSON_RPC.INVALID_PARAMS, 'resources/read requires a "uri" param'));
22623
+ try {
22624
+ const contents = readResource(uri, cfg, collections);
22625
+ return c.json(rpcResult(id, { contents: [contents] }));
22626
+ } catch (e) {
22627
+ if (e instanceof McpToolError) return c.json(rpcError(id, JSON_RPC.INVALID_PARAMS, e.message));
22628
+ throw e;
22629
+ }
22630
+ }
22631
+ default:
22632
+ return c.json(rpcError(id, JSON_RPC.METHOD_NOT_FOUND, `Method not found: ${method}`));
22633
+ }
22634
+ } catch (e) {
22635
+ console.error("[mcp] handler error:", e);
22636
+ return c.json(rpcError(id, JSON_RPC.INTERNAL_ERROR, "Internal error"));
22637
+ }
22638
+ });
22639
+ return app2;
22640
+ }
22641
+
22642
+ // src/plugins/core-plugins/mcp-plugin/admin/templates.ts
22643
+ init_admin_layout_catalyst_template();
22644
+ function accessBadge(ok) {
22645
+ return ok ? `<span class="inline-flex items-center rounded-full bg-emerald-50 dark:bg-emerald-900/20 px-2 py-0.5 text-xs font-medium text-emerald-700 dark:text-emerald-400 ring-1 ring-emerald-600/20 dark:ring-emerald-400/20">Yes</span>` : `<span class="inline-flex items-center rounded-full bg-zinc-100 dark:bg-zinc-800 px-2 py-0.5 text-xs font-medium text-zinc-500 dark:text-zinc-400 ring-1 ring-zinc-200 dark:ring-zinc-700">No</span>`;
22646
+ }
22647
+ function toolNamesForType(typeId, tools) {
22648
+ const names = tools.filter((t) => t.typeId === typeId).map((t) => `<code class="text-xs font-mono text-zinc-500 dark:text-zinc-400">${escapeHtml(t.name)}</code>`);
22649
+ return names.length ? names.join('<span class="text-zinc-300 dark:text-zinc-600 mx-1">,</span>') : "\u2014";
22650
+ }
22651
+ function buildClaudeJson(endpointUrl) {
22652
+ return JSON.stringify(
22653
+ {
22654
+ mcpServers: {
22655
+ sonicjs: {
22656
+ type: "http",
22657
+ url: endpointUrl,
22658
+ headers: { Authorization: "Bearer sk_YOUR_API_KEY" }
22659
+ }
22660
+ }
22661
+ },
22662
+ null,
22663
+ 2
22664
+ );
22665
+ }
22666
+ function buildCursorJson(endpointUrl) {
22667
+ return JSON.stringify(
22668
+ {
22669
+ mcpServers: {
22670
+ sonicjs: {
22671
+ url: endpointUrl,
22672
+ headers: { Authorization: "Bearer sk_YOUR_API_KEY" }
22673
+ }
22674
+ }
22675
+ },
22676
+ null,
22677
+ 2
22678
+ );
22679
+ }
22680
+ function renderMcpDashboardPage(data) {
22681
+ const { endpointUrl, types, tools, user, version, dynamicMenuItems } = data;
22682
+ const safeEndpoint = escapeHtml(endpointUrl);
22683
+ const typeCount = types.length;
22684
+ const toolCount = tools.length;
22685
+ const typeRows = types.length ? types.map(
22686
+ (t) => `
22687
+ <tr class="border-t border-zinc-950/5 dark:border-white/10">
22688
+ <td class="py-3 pr-4 text-sm font-mono text-zinc-950 dark:text-white">${escapeHtml(t.typeId)}</td>
22689
+ <td class="py-3 pr-4 text-sm text-zinc-500 dark:text-zinc-400">${escapeHtml(t.displayName)}</td>
22690
+ <td class="py-3 pr-4">${accessBadge(t.read)}</td>
22691
+ <td class="py-3 pr-4">${accessBadge(t.write)}</td>
22692
+ <td class="py-3 text-sm">${toolNamesForType(t.typeId, tools)}</td>
22693
+ </tr>`
22694
+ ).join("") : `<tr><td colspan="5" class="py-8 text-center text-sm text-zinc-500 dark:text-zinc-400">No collections exposed. Register collections via <code class="font-mono">registerCollections()</code> in your app entry point.</td></tr>`;
22695
+ const claudeJson = escapeHtml(buildClaudeJson(endpointUrl));
22696
+ const cursorJson = escapeHtml(buildCursorJson(endpointUrl));
22697
+ const content2 = `
22698
+ <div class="space-y-8">
22699
+
22700
+ <!-- Header -->
22701
+ <div class="sm:flex sm:items-center sm:justify-between">
22702
+ <div class="sm:flex-auto">
22703
+ <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">MCP Server</h1>
22704
+ <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
22705
+ Model Context Protocol endpoint for AI agents (Claude Code, Cursor, VS Code). Requests are authenticated with API keys.
22706
+ </p>
22707
+ </div>
22708
+ <div class="mt-4 sm:mt-0 sm:ml-16">
22709
+ <a href="/admin/plugins/api-keys"
22710
+ class="inline-flex items-center justify-center rounded-lg bg-cyan-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-cyan-500 transition-colors shadow-sm">
22711
+ Mint API Key
22712
+ </a>
22713
+ </div>
22714
+ </div>
22715
+
22716
+ <!-- Status -->
22717
+ <div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl p-6 ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm">
22718
+ <h2 class="text-sm font-semibold text-zinc-950 dark:text-white mb-4">Endpoint</h2>
22719
+ <div class="flex items-center gap-3">
22720
+ <code id="endpoint-url" class="flex-1 rounded-lg bg-zinc-50 dark:bg-zinc-800 px-3 py-2 text-sm font-mono text-zinc-950 dark:text-white ring-1 ring-zinc-950/5 dark:ring-white/5 break-all">${safeEndpoint}</code>
22721
+ <button type="button" onclick="copyEndpoint()"
22722
+ class="shrink-0 rounded-lg bg-zinc-100 dark:bg-zinc-800 px-3 py-2 text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-700 ring-1 ring-zinc-950/5 dark:ring-white/10 transition-colors">
22723
+ Copy
22724
+ </button>
22725
+ </div>
22726
+ <div class="mt-4 flex flex-wrap gap-4 text-sm text-zinc-500 dark:text-zinc-400">
22727
+ <span>
22728
+ <span class="font-semibold text-zinc-950 dark:text-white">${typeCount}</span>
22729
+ collection${typeCount === 1 ? "" : "s"} exposed
22730
+ </span>
22731
+ <span class="text-zinc-300 dark:text-zinc-600">&middot;</span>
22732
+ <span>
22733
+ <span class="font-semibold text-zinc-950 dark:text-white">${toolCount}</span>
22734
+ tool${toolCount === 1 ? "" : "s"} available
22735
+ </span>
22736
+ <span class="text-zinc-300 dark:text-zinc-600">&middot;</span>
22737
+ <span>Auth via <a href="/admin/plugins/api-keys" class="text-cyan-600 dark:text-cyan-400 hover:underline">API Keys</a></span>
22738
+ </div>
22739
+ </div>
22740
+
22741
+ <!-- Exposed collections table -->
22742
+ <div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl p-6 ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm overflow-x-auto">
22743
+ <h2 class="text-sm font-semibold text-zinc-950 dark:text-white mb-4">Exposed collections</h2>
22744
+ <table class="w-full text-left">
22745
+ <thead>
22746
+ <tr class="text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
22747
+ <th class="pb-2 pr-4">ID</th>
22748
+ <th class="pb-2 pr-4">Display name</th>
22749
+ <th class="pb-2 pr-4">Read</th>
22750
+ <th class="pb-2 pr-4">Write</th>
22751
+ <th class="pb-2">Tools</th>
22752
+ </tr>
22753
+ </thead>
22754
+ <tbody>${typeRows}</tbody>
22755
+ </table>
22756
+ </div>
22757
+
22758
+ <!-- Integration guide -->
22759
+ <div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl p-6 ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm space-y-6">
22760
+ <div>
22761
+ <h2 class="text-sm font-semibold text-zinc-950 dark:text-white mb-1">Integration guide</h2>
22762
+ <p class="text-sm text-zinc-500 dark:text-zinc-400">
22763
+ <a href="/admin/plugins/api-keys" class="text-cyan-600 dark:text-cyan-400 hover:underline">Mint an API key</a>,
22764
+ then paste one of the configs below into your MCP client.
22765
+ Replace <code class="font-mono text-xs">sk_YOUR_API_KEY</code> with the key.
22766
+ </p>
22767
+ </div>
22768
+
22769
+ <!-- Claude Code -->
22770
+ <div>
22771
+ <div class="flex items-center justify-between mb-2">
22772
+ <h3 class="text-sm font-medium text-zinc-700 dark:text-zinc-300">
22773
+ Claude Code &mdash; <code class="font-mono text-xs">~/.claude/mcp.json</code>
22774
+ </h3>
22775
+ <button type="button" onclick="copyConfig('claude-config')"
22776
+ class="text-xs text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors">
22777
+ Copy
22778
+ </button>
22779
+ </div>
22780
+ <pre id="claude-config" class="rounded-lg bg-zinc-950 dark:bg-zinc-800 px-4 py-3 text-sm font-mono text-zinc-100 overflow-x-auto ring-1 ring-zinc-700 dark:ring-zinc-600 whitespace-pre">${claudeJson}</pre>
22781
+ </div>
22782
+
22783
+ <!-- Cursor -->
22784
+ <div>
22785
+ <div class="flex items-center justify-between mb-2">
22786
+ <h3 class="text-sm font-medium text-zinc-700 dark:text-zinc-300">
22787
+ Cursor &mdash; <code class="font-mono text-xs">~/.cursor/mcp.json</code>
22788
+ </h3>
22789
+ <button type="button" onclick="copyConfig('cursor-config')"
22790
+ class="text-xs text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors">
22791
+ Copy
22792
+ </button>
22793
+ </div>
22794
+ <pre id="cursor-config" class="rounded-lg bg-zinc-950 dark:bg-zinc-800 px-4 py-3 text-sm font-mono text-zinc-100 overflow-x-auto ring-1 ring-zinc-700 dark:ring-zinc-600 whitespace-pre">${cursorJson}</pre>
22795
+ </div>
22796
+
22797
+ <p class="text-xs text-zinc-400 dark:text-zinc-500">
22798
+ MCP v1 = HTTP POST only (no SSE stream). Clients that require stdio transport can use
22799
+ <code class="font-mono">npx mcp-remote ${safeEndpoint}</code> as a local bridge.
22800
+ </p>
22801
+ </div>
22802
+
22803
+ </div>
22804
+
22805
+ <script>
22806
+ function copyEndpoint() {
22807
+ navigator.clipboard.writeText(document.getElementById('endpoint-url').textContent.trim()).then(() => {
22808
+ window.showNotification && window.showNotification('Endpoint URL copied', 'success')
22809
+ })
22810
+ }
22811
+ function copyConfig(id) {
22812
+ navigator.clipboard.writeText(document.getElementById(id).textContent).then(() => {
22813
+ window.showNotification && window.showNotification('Config copied', 'success')
22814
+ })
22815
+ }
22816
+ </script>`;
22817
+ const layoutData = {
22818
+ title: "MCP Server",
22819
+ pageTitle: "MCP Server",
22820
+ currentPath: "/admin/mcp",
22821
+ user,
22822
+ content: content2,
22823
+ version,
22824
+ dynamicMenuItems
22825
+ };
22826
+ return renderAdminLayoutCatalyst(layoutData);
22827
+ }
22828
+
22829
+ // src/plugins/core-plugins/mcp-plugin/admin/routes.ts
22830
+ function createMcpAdminRoutes(options = {}) {
22831
+ const routes2 = new Hono();
22832
+ routes2.use("*", requireAuth());
22833
+ routes2.use("*", async (c, next) => {
22834
+ const user = c.get("user");
22835
+ if (user?.role !== "admin") return c.json({ error: "Access denied" }, 403);
22836
+ return next();
22837
+ });
22838
+ routes2.get("/", async (c) => {
22839
+ const url = new URL(c.req.url);
22840
+ const endpointUrl = `${url.protocol}//${url.host}/mcp`;
22841
+ const registry = getCollectionRegistry();
22842
+ const active = registry.listActive();
22843
+ const collections = new Map(active.map((col) => [col.name, col]));
22844
+ const cfg = resolveMcpConfig(options, active);
22845
+ const tools = buildToolRegistry(cfg, collections, PHASE_FLAGS);
22846
+ const user = c.get("user");
22847
+ return c.html(
22848
+ renderMcpDashboardPage({
22849
+ endpointUrl,
22850
+ types: cfg.types,
22851
+ tools,
22852
+ user: { name: user.email, email: user.email, role: user.role },
22853
+ version: c.get("appVersion"),
22854
+ dynamicMenuItems: c.get("pluginMenuItems")
22855
+ })
22856
+ );
22857
+ });
22858
+ return routes2;
22859
+ }
22860
+
22861
+ // src/plugins/core-plugins/mcp-plugin/index.ts
22862
+ var MCP_ICON = `<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>`;
22863
+ function mcpPlugin(options = {}) {
22864
+ return definePlugin({
22865
+ id: "mcp",
22866
+ version: "1.0.0",
22867
+ name: "MCP Server",
22868
+ description: "Exposes SonicJS content as Model Context Protocol tools for AI agents (Claude Code, Cursor, \u2026).",
22869
+ sonicjsVersionRange: "^3.0.0",
22870
+ author: { name: "SonicJS Team" },
22871
+ register(app2) {
22872
+ app2.route("/mcp", createMcpRoutes(options));
22873
+ app2.route("/admin/mcp", createMcpAdminRoutes(options));
22874
+ },
22875
+ menu: [{ label: "MCP Server", path: "/admin/mcp", icon: MCP_ICON, order: 87 }]
22876
+ });
22877
+ }
22878
+ function createMcpPlugin(options) {
22879
+ return mcpPlugin(options);
22880
+ }
22054
22881
 
22055
22882
  // src/index.ts
22056
22883
  var VERSION = package_default.version;
22057
22884
 
22058
- export { BUILT_IN_PROVIDERS, CloudflareEmailProvider2 as CloudflareEmailProvider, OAuthService, VERSION, createDb, createHelloWorldPlugin, createOAuthProvidersPlugin, createRedirectPlugin, createSonicJSApp, emailReconciliationPlugin, generateTriggersComment, helloWorldPlugin, oauthProvidersPlugin, parseCronTriggers, redirectPlugin, setupCoreMiddleware, setupCoreRoutes, site_settings_collection_default as siteSettingsCollection, updateWranglerTriggers };
22885
+ export { BUILT_IN_PROVIDERS, CloudflareEmailProvider2 as CloudflareEmailProvider, OAuthService, VERSION, createDb, createHelloWorldPlugin, createMcpPlugin, createOAuthProvidersPlugin, createRedirectPlugin, createSonicJSApp, emailReconciliationPlugin, generateTriggersComment, helloWorldPlugin, mcpPlugin, oauthProvidersPlugin, parseCronTriggers, redirectPlugin, setupCoreMiddleware, setupCoreRoutes, site_settings_collection_default as siteSettingsCollection, updateWranglerTriggers };
22059
22886
  //# sourceMappingURL=index.js.map
22060
22887
  //# sourceMappingURL=index.js.map