webspresso 0.0.91 → 0.0.93

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 (60) hide show
  1. package/README.md +103 -2864
  2. package/bin/commands/admin-password.js +8 -2
  3. package/bin/commands/api.js +7 -1
  4. package/bin/commands/favicon-generate.js +6 -4
  5. package/bin/commands/mcp.js +162 -0
  6. package/bin/commands/new.js +7 -1
  7. package/bin/commands/page.js +7 -1
  8. package/bin/commands/seed.js +7 -1
  9. package/bin/commands/service.js +8 -2
  10. package/bin/commands/skill.js +7 -1
  11. package/bin/webspresso.js +2 -0
  12. package/core/compileSchema.js +1 -5
  13. package/core/compression/compressible.js +17 -5
  14. package/core/compression/compression-stream.js +1 -1
  15. package/core/compression/middleware.js +9 -0
  16. package/core/compression/negotiate.js +23 -11
  17. package/core/kernel/app.js +2 -1
  18. package/core/kernel/index.js +5 -1
  19. package/core/orm/cache/fingerprint.js +23 -5
  20. package/core/orm/eager-loader.js +54 -9
  21. package/core/orm/index.js +38 -2
  22. package/core/orm/model.js +11 -2
  23. package/core/queue/adapters/database.js +46 -13
  24. package/core/queue/adapters/memory.js +21 -0
  25. package/core/queue/adapters/redis.js +21 -0
  26. package/core/queue/manager.js +46 -20
  27. package/core/validation/index.js +70 -0
  28. package/index.d.ts +99 -5
  29. package/index.js +7 -1
  30. package/package.json +9 -1
  31. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +6 -1
  32. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +18 -8
  33. package/plugins/admin-panel/client/parts/07-model-list.js +9 -4
  34. package/plugins/admin-panel/client/parts/08-record-list.js +20 -2
  35. package/plugins/admin-panel/index.js +65 -6
  36. package/plugins/admin-panel/modules/bulk-actions.js +9 -2
  37. package/plugins/admin-panel/modules/dashboard.js +8 -1
  38. package/plugins/data-exchange/export-xlsx.js +28 -6
  39. package/plugins/data-exchange/import.js +45 -20
  40. package/plugins/data-exchange/index.js +5 -0
  41. package/plugins/data-exchange/parse-table.js +14 -5
  42. package/plugins/data-exchange/record-selection.js +2 -0
  43. package/plugins/index.js +4 -0
  44. package/plugins/mcp/auth.js +104 -0
  45. package/plugins/mcp/discovery.js +88 -0
  46. package/plugins/mcp/index.js +208 -0
  47. package/plugins/mcp/prompts.js +149 -0
  48. package/plugins/mcp/providers/orm.js +325 -0
  49. package/plugins/mcp/providers/services.js +190 -0
  50. package/plugins/mcp/providers/system.js +87 -0
  51. package/plugins/mcp/server.js +472 -0
  52. package/plugins/mcp/transports/sse.js +139 -0
  53. package/plugins/mcp/transports/stdio.js +108 -0
  54. package/src/file-router.js +184 -123
  55. package/src/njk-frontmatter.js +24 -14
  56. package/src/server.js +30 -12
  57. package/src/services/discovery.js +2 -1
  58. package/src/services/registry.js +2 -0
  59. package/src/services/validator.js +2 -53
  60. package/templates/skills/webspresso-usage/REFERENCE-framework.md +6 -4
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Webspresso MCP File-Based Auto-Discovery
3
+ * Discovers custom MCP tools, resources, and prompts from project `mcp/` directory
4
+ * @module plugins/mcp/discovery
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ /**
11
+ * Scan a directory for JS files and require them
12
+ * @param {string} dirPath
13
+ * @returns {Array<Object>}
14
+ */
15
+ function scanModuleDir(dirPath) {
16
+ if (!fs.existsSync(dirPath)) return [];
17
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
18
+ const items = [];
19
+
20
+ for (const entry of entries) {
21
+ if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.cjs'))) {
22
+ if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
23
+ const fullPath = path.join(dirPath, entry.name);
24
+ try {
25
+ const mod = require(fullPath);
26
+ if (mod && typeof mod === 'object') {
27
+ // If mod doesn't have a name, default to filename without extension
28
+ if (!mod.name && !mod.uri) {
29
+ const baseName = path.basename(entry.name, path.extname(entry.name));
30
+ mod.name = baseName;
31
+ }
32
+ items.push(mod);
33
+ }
34
+ } catch (err) {
35
+ console.warn(`[mcp-discovery] Failed to load module at ${fullPath}:`, err.message);
36
+ }
37
+ }
38
+ }
39
+
40
+ return items;
41
+ }
42
+
43
+ /**
44
+ * Discovers custom MCP components from the filesystem and registers them to the server
45
+ * @param {import('./server').McpServer} server
46
+ * @param {string} [rootDir=process.cwd()]
47
+ * @param {string} [mcpDirName='mcp']
48
+ */
49
+ function discoverMcpDirectory(server, rootDir = process.cwd(), mcpDirName = 'mcp') {
50
+ const mcpRoot = path.resolve(rootDir, mcpDirName);
51
+ if (!fs.existsSync(mcpRoot)) return;
52
+
53
+ // 1. Discover custom tools: mcp/tools/
54
+ const toolsDir = path.join(mcpRoot, 'tools');
55
+ const customTools = scanModuleDir(toolsDir);
56
+ for (const tool of customTools) {
57
+ if (typeof tool.handler === 'function') {
58
+ server.registerTool(tool);
59
+ }
60
+ }
61
+
62
+ // 2. Discover custom resources: mcp/resources/
63
+ const resourcesDir = path.join(mcpRoot, 'resources');
64
+ const customResources = scanModuleDir(resourcesDir);
65
+ for (const res of customResources) {
66
+ if (typeof res.handler === 'function') {
67
+ if (res.uriTemplate) {
68
+ server.registerResourceTemplate(res);
69
+ } else if (res.uri) {
70
+ server.registerResource(res);
71
+ }
72
+ }
73
+ }
74
+
75
+ // 3. Discover custom prompts: mcp/prompts/
76
+ const promptsDir = path.join(mcpRoot, 'prompts');
77
+ const customPrompts = scanModuleDir(promptsDir);
78
+ for (const prompt of customPrompts) {
79
+ if (typeof prompt.handler === 'function') {
80
+ server.registerPrompt(prompt);
81
+ }
82
+ }
83
+ }
84
+
85
+ module.exports = {
86
+ discoverMcpDirectory,
87
+ scanModuleDir,
88
+ };
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Webspresso Model Context Protocol (MCP) Plugin
3
+ * Connects Webspresso applications to AI agents, IDEs, and LLM tools.
4
+ * @module plugins/mcp
5
+ */
6
+
7
+ const { McpServer } = require('./server');
8
+ const { createServiceTools } = require('./providers/services');
9
+ const { createOrmTools, createOrmResources } = require('./providers/orm');
10
+ const { createSystemResources } = require('./providers/system');
11
+ const { createBuiltinPrompts } = require('./prompts');
12
+ const { createMcpAuthMiddleware } = require('./auth');
13
+ const { mountSseTransport } = require('./transports/sse');
14
+ const { startStdioTransport, redirectConsoleToStderr, restoreConsole } = require('./transports/stdio');
15
+ const { discoverMcpDirectory } = require('./discovery');
16
+
17
+ const { getServiceRegistry, hasServiceRegistry } = require('../../src/app-context');
18
+
19
+ /**
20
+ * MCP Plugin Factory
21
+ * @param {Object} [options={}]
22
+ * @param {string} [options.path='/_mcp'] - Route path for HTTP & SSE endpoints
23
+ * @param {boolean} [options.enabled=true] - Whether the plugin is active
24
+ * @param {Object} [options.auth] - Authentication options { token, localhostOnly, verify }
25
+ * @param {Object|boolean} [options.services] - Services auto-bridge config
26
+ * @param {Object|boolean} [options.orm] - ORM auto-bridge config
27
+ * @param {Object|boolean} [options.system] - System resources config
28
+ * @param {Array<Object>} [options.tools] - Additional custom tools
29
+ * @param {Array<Object>} [options.resources] - Additional custom resources
30
+ * @param {Array<Object>} [options.prompts] - Additional custom prompts
31
+ * @param {boolean} [options.discovery=true] - Auto-discover mcp/ folder
32
+ * @param {string} [options.name='webspresso-mcp'] - Server name
33
+ * @param {string} [options.version='1.0.0'] - Server version
34
+ * @param {string} [options.instructions] - Instructions for the LLM
35
+ * @returns {Object} Webspresso plugin definition
36
+ */
37
+ function mcpPlugin(options = {}) {
38
+ const {
39
+ path = '/_mcp',
40
+ enabled = true,
41
+ auth = {},
42
+ services: servicesOption = true,
43
+ orm: ormOption = true,
44
+ system: systemOption = true,
45
+ tools: extraTools = [],
46
+ resources: extraResources = [],
47
+ prompts: extraPrompts = [],
48
+ discovery = true,
49
+ name = 'webspresso-mcp',
50
+ version = '1.0.0',
51
+ instructions,
52
+ } = options;
53
+
54
+ let serverInstance = null;
55
+
56
+ return {
57
+ name: 'mcp',
58
+ version: '1.0.0',
59
+ dependencies: [],
60
+
61
+ api: {
62
+ getServer: () => serverInstance,
63
+ registerTool: (tool) => serverInstance && serverInstance.registerTool(tool),
64
+ registerResource: (res) => serverInstance && serverInstance.registerResource(res),
65
+ registerPrompt: (prompt) => serverInstance && serverInstance.registerPrompt(prompt),
66
+ },
67
+
68
+ register(ctx) {
69
+ if (!enabled) return;
70
+
71
+ serverInstance = new McpServer({
72
+ name,
73
+ version,
74
+ instructions,
75
+ context: {
76
+ app: ctx.app,
77
+ },
78
+ });
79
+
80
+ // Register initial custom tools, resources, prompts
81
+ if (Array.isArray(extraTools)) {
82
+ serverInstance.registerTools(extraTools);
83
+ }
84
+ if (Array.isArray(extraResources)) {
85
+ for (const res of extraResources) {
86
+ if (res.uriTemplate) {
87
+ serverInstance.registerResourceTemplate(res);
88
+ } else {
89
+ serverInstance.registerResource(res);
90
+ }
91
+ }
92
+ }
93
+ if (Array.isArray(extraPrompts)) {
94
+ for (const prompt of extraPrompts) {
95
+ serverInstance.registerPrompt(prompt);
96
+ }
97
+ }
98
+
99
+ // Register built-in prompts
100
+ const builtinPrompts = createBuiltinPrompts();
101
+ for (const p of builtinPrompts) {
102
+ serverInstance.registerPrompt(p);
103
+ }
104
+
105
+ // Expose to ctx
106
+ ctx.mcp = {
107
+ server: serverInstance,
108
+ registerTool: (t) => serverInstance.registerTool(t),
109
+ registerResource: (r) => serverInstance.registerResource(r),
110
+ registerPrompt: (p) => serverInstance.registerPrompt(p),
111
+ };
112
+ },
113
+
114
+ onRoutesReady(ctx) {
115
+ if (!enabled || !serverInstance) return;
116
+
117
+ const { app, db, routes } = ctx;
118
+ serverInstance.context.db = db;
119
+
120
+ // 1. Resolve ServiceRegistry
121
+ let registry = null;
122
+ if (ctx.options?.serviceRegistry) {
123
+ registry = ctx.options.serviceRegistry;
124
+ } else if (hasServiceRegistry()) {
125
+ registry = getServiceRegistry();
126
+ }
127
+ serverInstance.context.serviceRegistry = registry;
128
+
129
+ // 2. Auto-bridge Services to MCP Tools
130
+ if (servicesOption && registry) {
131
+ const sOpts = typeof servicesOption === 'object' ? servicesOption : {};
132
+ const serviceTools = createServiceTools({
133
+ registry,
134
+ include: sOpts.include,
135
+ exclude: sOpts.exclude,
136
+ readOnly: Boolean(sOpts.readOnly),
137
+ role: sOpts.role || 'admin',
138
+ });
139
+ serverInstance.registerTools(serviceTools);
140
+ }
141
+
142
+ // 3. Auto-bridge ORM Models to MCP Tools and Resources
143
+ if (ormOption && db) {
144
+ const oOpts = typeof ormOption === 'object' ? ormOption : {};
145
+ const ormTools = createOrmTools({
146
+ db,
147
+ readOnly: Boolean(oOpts.readOnly),
148
+ models: oOpts.models,
149
+ });
150
+ serverInstance.registerTools(ormTools);
151
+
152
+ const { resources, templates } = createOrmResources({ db });
153
+ for (const r of resources) {
154
+ serverInstance.registerResource(r);
155
+ }
156
+ for (const t of templates) {
157
+ serverInstance.registerResourceTemplate(t);
158
+ }
159
+ }
160
+
161
+ // 4. Auto-bridge System Resources
162
+ if (systemOption) {
163
+ const sysResources = createSystemResources({
164
+ appContext: ctx,
165
+ serviceRegistry: registry,
166
+ routes,
167
+ db,
168
+ });
169
+ for (const r of sysResources) {
170
+ serverInstance.registerResource(r);
171
+ }
172
+ }
173
+
174
+ // 5. Auto-discover custom items from mcp/ directory
175
+ if (discovery) {
176
+ discoverMcpDirectory(serverInstance, process.cwd());
177
+ }
178
+
179
+ // 6. Mount HTTP & SSE Transport on Express
180
+ const authMiddleware =
181
+ auth.token || auth.localhostOnly || auth.verify ? createMcpAuthMiddleware(auth) : null;
182
+
183
+ mountSseTransport({
184
+ app,
185
+ server: serverInstance,
186
+ path,
187
+ authMiddleware,
188
+ });
189
+ },
190
+ };
191
+ }
192
+
193
+ module.exports = {
194
+ mcpPlugin,
195
+ McpServer,
196
+ createMcpServer: (opts) => new McpServer(opts),
197
+ startStdioTransport,
198
+ mountSseTransport,
199
+ createMcpAuthMiddleware,
200
+ createServiceTools,
201
+ createOrmTools,
202
+ createOrmResources,
203
+ createSystemResources,
204
+ createBuiltinPrompts,
205
+ discoverMcpDirectory,
206
+ redirectConsoleToStderr,
207
+ restoreConsole,
208
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Webspresso MCP Built-in Prompt Templates
3
+ * Provides standard LLM prompt templates following Webspresso architectural guidelines.
4
+ * @module plugins/mcp/prompts
5
+ */
6
+
7
+ /**
8
+ * Generates built-in prompt definitions
9
+ * @returns {Array<Object>}
10
+ */
11
+ function createBuiltinPrompts() {
12
+ return [
13
+ {
14
+ name: 'scaffold_service',
15
+ description: 'Generates a Webspresso service file adhering to strict architecture guidelines.',
16
+ arguments: [
17
+ {
18
+ name: 'serviceName',
19
+ description: 'Dot-notation service name (e.g. "order.cancel" or "user.invite")',
20
+ required: true,
21
+ },
22
+ {
23
+ name: 'description',
24
+ description: 'Brief description of what the service does',
25
+ required: false,
26
+ },
27
+ ],
28
+ handler: async ({ serviceName, description = '' }) => {
29
+ const parts = serviceName.split('.');
30
+ const domain = parts.slice(0, -1).join('/');
31
+ const action = parts[parts.length - 1];
32
+ const filePath = `services/${domain ? `${domain}/` : ''}${action}.js`;
33
+
34
+ return {
35
+ messages: [
36
+ {
37
+ role: 'user',
38
+ content: {
39
+ type: 'text',
40
+ text: `Please generate a Webspresso service file for "${serviceName}" at path "${filePath}".
41
+ Description: ${description || 'N/A'}
42
+
43
+ Follow Webspresso rules:
44
+ - Export an object with { schema, auth, timeout, transaction, handler }.
45
+ - Use Zod (const { z } = require('zod');) for schema validation.
46
+ - Set transaction: true if multi-step mutation.
47
+ - Use req.db / ctx.db.getRepository('ModelName') to access data. NEVER write raw knex table queries.
48
+ - Throw semantic errors: NotFoundError, ValidationError, UnauthorizedError, ForbiddenError.
49
+ - Do NOT use controllers.`,
50
+ },
51
+ },
52
+ ],
53
+ };
54
+ },
55
+ },
56
+ {
57
+ name: 'scaffold_model',
58
+ description: 'Generates an ORM model file using defineModel and zdb schema builder.',
59
+ arguments: [
60
+ {
61
+ name: 'modelName',
62
+ description: 'PascalCase model name (e.g. "Product", "Invoice")',
63
+ required: true,
64
+ },
65
+ {
66
+ name: 'tableName',
67
+ description: 'Database table name (e.g. "products", "invoices")',
68
+ required: false,
69
+ },
70
+ ],
71
+ handler: async ({ modelName, tableName }) => {
72
+ const table = tableName || `${modelName.toLowerCase()}s`;
73
+ const filePath = `models/${modelName.toLowerCase()}.js`;
74
+
75
+ return {
76
+ messages: [
77
+ {
78
+ role: 'user',
79
+ content: {
80
+ type: 'text',
81
+ text: `Please generate a Webspresso ORM model for "${modelName}" (table: "${table}") at path "${filePath}".
82
+
83
+ Follow Webspresso rules:
84
+ - Use const { defineModel, zdb } = require('webspresso');
85
+ - Use defineModel({ name, table, schema, relations, scopes, hidden, admin }).
86
+ - Build schema with zdb: zdb.id(), zdb.string(), zdb.integer(), zdb.boolean(), zdb.json(), zdb.dateTime().
87
+ - Define relations if relevant (hasMany, belongsTo).
88
+ - Enable admin: { enabled: true, icon: '...' } if appropriate.`,
89
+ },
90
+ },
91
+ ],
92
+ };
93
+ },
94
+ },
95
+ {
96
+ name: 'scaffold_api',
97
+ description: 'Generates a file-based API route module inside pages/api/...',
98
+ arguments: [
99
+ {
100
+ name: 'endpoint',
101
+ description: 'API endpoint route (e.g. "/api/notes" or "/api/notes/[id]")',
102
+ required: true,
103
+ },
104
+ {
105
+ name: 'method',
106
+ description: 'HTTP method (GET, POST, PUT, PATCH, DELETE)',
107
+ required: true,
108
+ },
109
+ ],
110
+ handler: async ({ endpoint, method }) => {
111
+ const m = method.toLowerCase();
112
+ let relativePath = endpoint.replace(/^\/api\//, '');
113
+ const filePath = `pages/api/${relativePath}.${m}.js`;
114
+
115
+ return {
116
+ messages: [
117
+ {
118
+ role: 'user',
119
+ content: {
120
+ type: 'text',
121
+ text: `Please generate a Webspresso API route file at "${filePath}".
122
+ Endpoint: ${method.toUpperCase()} ${endpoint}
123
+
124
+ Follow Webspresso rules:
125
+ - 100% File-Based Routing: NEVER create a routes/ folder or use express.Router().
126
+ - Standard API Route Module Contract:
127
+ module.exports = {
128
+ schema: ({ z }) => ({
129
+ body: z.object({...}),
130
+ params: z.object({...}),
131
+ }),
132
+ middleware: [],
133
+ handler: async (req, res) => {
134
+ // req.db and req.service are injected
135
+ // req.input contains validated { body, query, params }
136
+ }
137
+ };`,
138
+ },
139
+ },
140
+ ],
141
+ };
142
+ },
143
+ },
144
+ ];
145
+ }
146
+
147
+ module.exports = {
148
+ createBuiltinPrompts,
149
+ };