@donkeylabs/mcp 0.4.2 → 0.4.3

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.ts +83 -48
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/mcp",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "MCP server for AI-assisted development with @donkeylabs/server",
5
5
  "type": "module",
6
6
  "main": "./src/server.ts",
package/src/server.ts CHANGED
@@ -282,6 +282,13 @@ function findDocsDir(): string {
282
282
  const DOCS_DIR = findDocsDir();
283
283
 
284
284
  const RESOURCES = [
285
+ {
286
+ uri: "donkeylabs://docs/database",
287
+ name: "Database (Kysely)",
288
+ description: "Kysely queries, CRUD operations, joins, transactions, migrations",
289
+ mimeType: "text/markdown",
290
+ docFile: "database.md",
291
+ },
285
292
  {
286
293
  uri: "donkeylabs://docs/project-structure",
287
294
  name: "Project Structure",
@@ -939,6 +946,25 @@ async function getArchitectureGuidance(args: { task: string }): Promise<string>
939
946
 
940
947
  let guidance = `# Architecture Guidance\n\n**Task:** ${task}\n\n`;
941
948
 
949
+ // Always include the Plugin vs Route decision guidance
950
+ guidance += `## When to Create a Plugin vs Route\n\n`;
951
+ guidance += `**Core Principle:** Plugins = Reusable Business Logic | Routes = App-Specific API Endpoints\n\n`;
952
+ guidance += `### Create a Plugin when:\n`;
953
+ guidance += `- The logic could be **reused** across multiple routes or apps (auth, email, payments)\n`;
954
+ guidance += `- You need **database tables** for a domain concept (users, orders, products)\n`;
955
+ guidance += `- The functionality is **self-contained** with its own data and operations\n`;
956
+ guidance += `- You're building **cross-cutting concerns** like middleware\n\n`;
957
+ guidance += `### Create a Route when:\n`;
958
+ guidance += `- **Exposing plugin functionality** via HTTP (thin wrapper calling plugin methods)\n`;
959
+ guidance += `- **Combining multiple plugins** for a specific use case\n`;
960
+ guidance += `- Building **app-specific endpoints** that don't need reuse\n`;
961
+ guidance += `- **Simple operations** that don't warrant a full plugin\n\n`;
962
+ guidance += `### Workflow:\n`;
963
+ guidance += `1. Identify reusable domains → Create plugins with business logic\n`;
964
+ guidance += `2. Create routes that use plugins to expose functionality\n`;
965
+ guidance += `3. Keep routes thin - delegate to plugin service methods\n\n`;
966
+ guidance += `---\n\n`;
967
+
942
968
  // Pattern matching for common tasks
943
969
  if (taskLower.includes("auth") || taskLower.includes("login") || taskLower.includes("user")) {
944
970
  guidance += `## Recommended Approach: Authentication System\n\n`;
@@ -1073,10 +1099,11 @@ async function getArchitectureGuidance(args: { task: string }): Promise<string>
1073
1099
  }
1074
1100
 
1075
1101
  guidance += `\n## Documentation Resources\n`;
1076
- guidance += `- \`donkeylabs://docs/plugins\` - Plugin patterns\n`;
1077
- guidance += `- \`donkeylabs://docs/router\` - Route patterns\n`;
1078
- guidance += `- \`donkeylabs://docs/api-client\` - Client usage & generation\n`;
1079
- guidance += `- \`donkeylabs://docs/project-structure\` - Best practices\n`;
1102
+ guidance += `- \`donkeylabs://docs/database\` - Kysely queries, CRUD, joins, transactions\n`;
1103
+ guidance += `- \`donkeylabs://docs/plugins\` - Plugin patterns & When to Create a Plugin vs Route\n`;
1104
+ guidance += `- \`donkeylabs://docs/router\` - Route patterns & best practices\n`;
1105
+ guidance += `- \`donkeylabs://docs/api-client\` - Generated client usage\n`;
1106
+ guidance += `- \`donkeylabs://docs/handlers\` - Class-based handlers\n`;
1080
1107
 
1081
1108
  return guidance;
1082
1109
  }
@@ -1153,7 +1180,9 @@ async function createPlugin(args: {
1153
1180
  // Build the createPlugin chain
1154
1181
  let createPluginChain = "createPlugin";
1155
1182
  if (hasSchema) {
1156
- createPluginChain += `\n .withSchema<${toPascalCase(name)}Schema>()`;
1183
+ // Note: DB type is auto-generated by kysely-codegen from migrations
1184
+ // Use {} until `donkeylabs generate` creates schema.ts, then change to DB
1185
+ createPluginChain += `\n .withSchema<{}>() // Change to <DB> after running \`donkeylabs generate\``;
1157
1186
  }
1158
1187
  if (hasConfig) {
1159
1188
  createPluginChain += `\n .withConfig<${toPascalCase(name)}Config>()`;
@@ -1162,11 +1191,14 @@ async function createPlugin(args: {
1162
1191
 
1163
1192
  // Generate imports
1164
1193
  let imports = `import { createPlugin } from "@donkeylabs/server";\n`;
1194
+ imports += `import { z } from "zod";\n`;
1165
1195
  if (dependencies.length > 0) {
1166
1196
  imports += dependencies.map(d => `import { ${d}Plugin } from "../${d}";`).join("\n") + "\n";
1167
1197
  }
1168
1198
  if (hasSchema) {
1169
- imports += `import type { ${toPascalCase(name)}Schema } from "./schema";\n`;
1199
+ // Note: schema.ts is auto-generated after running `donkeylabs generate`
1200
+ // Once generated, uncomment this import:
1201
+ imports += `// import type { DB } from "./schema"; // Uncomment after running \`donkeylabs generate\`\n`;
1170
1202
  }
1171
1203
 
1172
1204
  // Generate config interface if needed
@@ -1224,43 +1256,36 @@ ${ctxComment}
1224
1256
 
1225
1257
  await Bun.write(join(pluginDir, "index.ts"), indexContent);
1226
1258
 
1227
- // Create schema and migrations if needed
1259
+ // Create migrations folder if plugin has schema
1260
+ // Note: schema.ts is auto-generated by `donkeylabs generate` from migrations
1228
1261
  if (hasSchema) {
1229
1262
  mkdirSync(join(pluginDir, "migrations"), { recursive: true });
1230
1263
 
1231
- const schemaContent = `// Database schema types for ${name} plugin
1232
- // Run 'donkeylabs generate' after adding migrations to generate types
1264
+ const migrationContent = `import { Kysely, sql } from "kysely";
1233
1265
 
1234
- export interface ${toPascalCase(name)}Schema {
1235
- // Tables will be generated here
1266
+ /**
1267
+ * Migration: 001_initial
1268
+ * Created: ${new Date().toISOString()}
1269
+ * Plugin: ${name}
1270
+ */
1271
+
1272
+ export async function up(db: Kysely<any>): Promise<void> {
1273
+ // Create your tables here
1236
1274
  // Example:
1237
- // ${name}: {
1238
- // id: Generated<number>;
1239
- // name: string;
1240
- // created_at: string;
1241
- // };
1275
+ // await db.schema
1276
+ // .createTable("${name}")
1277
+ // .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
1278
+ // .addColumn("name", "text", (col) => col.notNull())
1279
+ // .addColumn("created_at", "text", (col) => col.defaultTo(sql\`CURRENT_TIMESTAMP\`))
1280
+ // .execute();
1281
+ }
1282
+
1283
+ export async function down(db: Kysely<any>): Promise<void> {
1284
+ // Drop your tables here
1285
+ // await db.schema.dropTable("${name}").execute();
1242
1286
  }
1243
1287
  `;
1244
- await Bun.write(join(pluginDir, "schema.ts"), schemaContent);
1245
-
1246
- const migrationContent = `-- Migration: 001_initial
1247
- -- Created: ${new Date().toISOString()}
1248
- -- Plugin: ${name}
1249
-
1250
- -- UP
1251
- -- Add your CREATE TABLE statements here
1252
- -- Example:
1253
- -- CREATE TABLE ${name} (
1254
- -- id INTEGER PRIMARY KEY AUTOINCREMENT,
1255
- -- name TEXT NOT NULL,
1256
- -- created_at TEXT DEFAULT CURRENT_TIMESTAMP
1257
- -- );
1258
-
1259
- -- DOWN
1260
- -- Add your DROP TABLE statements here
1261
- -- DROP TABLE IF EXISTS ${name};
1262
- `;
1263
- await Bun.write(join(pluginDir, "migrations", "001_initial.sql"), migrationContent);
1288
+ await Bun.write(join(pluginDir, "migrations", "001_initial.ts"), migrationContent);
1264
1289
  }
1265
1290
 
1266
1291
  // Build registration example based on plugin type
@@ -1401,25 +1426,35 @@ async function addMigration(args: {
1401
1426
  mkdirSync(migrationsDir, { recursive: true });
1402
1427
  }
1403
1428
 
1404
- // Find next migration number
1429
+ // Find next migration number - check both .ts and .sql files for backwards compatibility
1405
1430
  const existing = readdirSync(migrationsDir)
1406
- .filter((f) => f.endsWith(".sql"))
1431
+ .filter((f) => f.endsWith(".ts") || f.endsWith(".sql"))
1407
1432
  .map((f) => parseInt(f.split("_")[0], 10))
1408
1433
  .filter((n) => !isNaN(n));
1409
1434
 
1410
1435
  const nextNum = existing.length > 0 ? Math.max(...existing) + 1 : 1;
1411
1436
  const numStr = String(nextNum).padStart(3, "0");
1412
- const filename = `${numStr}_${migrationName}.sql`;
1437
+ const filename = `${numStr}_${migrationName}.ts`;
1438
+
1439
+ // Escape backticks in SQL for template literal
1440
+ const escapedUpSql = upSql.replace(/`/g, "\\`");
1441
+ const escapedDownSql = (downSql || "").replace(/`/g, "\\`");
1442
+
1443
+ const content = `import { Kysely, sql } from "kysely";
1413
1444
 
1414
- const content = `-- Migration: ${numStr}_${migrationName}
1415
- -- Created: ${new Date().toISOString()}
1416
- -- Plugin: ${pluginName}
1445
+ /**
1446
+ * Migration: ${numStr}_${migrationName}
1447
+ * Created: ${new Date().toISOString()}
1448
+ * Plugin: ${pluginName}
1449
+ */
1417
1450
 
1418
- -- UP
1419
- ${upSql}
1451
+ export async function up(db: Kysely<any>): Promise<void> {
1452
+ await sql\`${escapedUpSql}\`.execute(db);
1453
+ }
1420
1454
 
1421
- -- DOWN
1422
- ${downSql || "-- Add rollback SQL here"}
1455
+ export async function down(db: Kysely<any>): Promise<void> {
1456
+ ${escapedDownSql ? `await sql\`${escapedDownSql}\`.execute(db);` : "// Add rollback logic here"}
1457
+ }
1423
1458
  `;
1424
1459
 
1425
1460
  await Bun.write(join(migrationsDir, filename), content);
@@ -1431,7 +1466,7 @@ ${downSql || "-- Add rollback SQL here"}
1431
1466
 
1432
1467
  ### Next Steps
1433
1468
 
1434
- 1. Review the migration SQL
1469
+ 1. Review the migration
1435
1470
  2. Run \`donkeylabs generate\` to update schema types
1436
1471
  3. The migration will run automatically on server start
1437
1472
 
@@ -1589,7 +1624,7 @@ export class ${handlerClassName} implements Handler<Routes.${prefix}.${handlerNa
1589
1624
  this.ctx = ctx;
1590
1625
  }
1591
1626
 
1592
- async handle(input: Routes.${prefix}.${handlerName}["input"]): Promise<Routes.${prefix}.${handlerName}["output"]> {
1627
+ async handle(input: Routes.${prefix}.${handlerName}.Input): Promise<Routes.${prefix}.${handlerName}.Output> {
1593
1628
  ${handler}
1594
1629
  }
1595
1630
  }