create-basalt 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,6 +46,7 @@ Multi-tenancy? (Y/n) y
46
46
  Authentication? (Y/n) y
47
47
  Subscriptions / billing? (y/N) n
48
48
  Web UI (React + shadcn)? (y/N) n
49
+ MCP server (routes as AI-agent tools)? (y/N) n
49
50
  'basalt' CLI (code generators)? (y/N) n
50
51
  Install dependencies now? (y/N) y
51
52
  Initialize a git repository? (y/N) y
@@ -89,6 +90,7 @@ Usage: npm create basalt <name> [options]
89
90
  | `--billing` | off | Includes subscriptions/plans (`@basaltkit/subscriptions`, example `free` and `pro` plans) |
90
91
  | `--ui` | off | Generates the `web/` frontend (React + shadcn via `@basaltkit/admin-shadcn` + `@basaltkit/sdk`). **Forces pnpm** — see note below |
91
92
  | `--cli` | off | Generates the `basalt` CLI (`bin/basalt.ts`, `pnpm basalt` script, `make:*` generators from `@basaltkit/generator`) |
93
+ | `--mcp` | off | Exposes read-only routes as MCP tools (`@basaltkit/mcp`) over HTTP at `POST /mcp` — the overview and health endpoints are opted in via `meta.mcp` |
92
94
  | `--install` | off | Installs dependencies at the end (with the detected/chosen manager) |
93
95
  | `--git` | off | Runs `git init` + first commit ("Initial commit from create-basalt") |
94
96
  | `--pm=<manager>` | autodetect | Package manager: `pnpm` \| `npm` \| `yarn` \| `bun` |
@@ -202,6 +204,7 @@ Exported from `create-basalt` (in addition to the `create-basalt` executable):
202
204
  | `billing` | `boolean` | No | `false` | Include subscriptions |
203
205
  | `ui` | `boolean` | No | `false` | Generate the `web/` frontend |
204
206
  | `cli` | `boolean` | No | `false` | Generate the `basalt` CLI |
207
+ | `mcp` | `boolean` | No | `false` | Expose read-only routes as MCP tools at `/mcp` |
205
208
 
206
209
  `CreateProjectResult`:
207
210
 
package/dist/cli.js CHANGED
@@ -11,6 +11,7 @@ Options:
11
11
  --billing Include subscriptions/billing
12
12
  --ui Scaffold a web/ frontend (React + shadcn + SDK)
13
13
  --cli Scaffold the 'basalt' CLI (code generators + commands)
14
+ --mcp Expose read-only routes as MCP tools at /mcp
14
15
  --install Install dependencies after scaffolding
15
16
  --git Initialize a git repository with a first commit
16
17
  --pm=<manager> Package manager: pnpm | npm | yarn | bun (default: auto-detect)
@@ -26,6 +27,7 @@ function parseArgs(argv) {
26
27
  billing: false,
27
28
  ui: false,
28
29
  cli: false,
30
+ mcp: false,
29
31
  install: false,
30
32
  git: false,
31
33
  yes: false,
@@ -41,6 +43,8 @@ function parseArgs(argv) {
41
43
  flags.ui = true;
42
44
  else if (token === '--cli')
43
45
  flags.cli = true;
46
+ else if (token === '--mcp')
47
+ flags.mcp = true;
44
48
  else if (token === '--install')
45
49
  flags.install = true;
46
50
  else if (token === '--git')
@@ -91,6 +95,7 @@ if (!flags.yes && flags.name === undefined && stdin.isTTY) {
91
95
  flags.billing = result.billing;
92
96
  flags.ui = result.ui;
93
97
  flags.cli = result.cli;
98
+ flags.mcp = result.mcp;
94
99
  flags.install = result.install;
95
100
  flags.git = result.git;
96
101
  flags.pm = result.pm;
@@ -121,6 +126,7 @@ try {
121
126
  billing: flags.billing,
122
127
  ui: flags.ui,
123
128
  cli: flags.cli,
129
+ mcp: flags.mcp,
124
130
  });
125
131
  console.log(`\nCreated ${result.options.name} in ${result.dir}\n`);
126
132
  for (const file of result.files)
package/dist/index.d.ts CHANGED
@@ -11,6 +11,8 @@ export interface CreateProjectInput {
11
11
  ui?: boolean;
12
12
  /** Scaffold the `basalt` CLI entrypoint (code generators + commands). Default: false. */
13
13
  cli?: boolean;
14
+ /** Expose opted-in routes as MCP tools over HTTP at `/mcp`. Default: false. */
15
+ mcp?: boolean;
14
16
  }
15
17
  export interface CreateProjectResult {
16
18
  dir: string;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readdir, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join, resolve } from 'node:path';
3
- import { appTest, appTs, envExample, envTs, gitignore, basaltBin, packageJson, pnpmWorkspaceYaml, readme, routesTs, serverTs, tsconfigJson, } from './templates.js';
3
+ import { appTest, appTs, envExample, envTs, gitignore, mcpJson, basaltBin, packageJson, pnpmWorkspaceYaml, readme, routesTs, serverTs, tsconfigJson, } from './templates.js';
4
4
  import { uiFiles } from './templates-ui.js';
5
5
  export class TargetNotEmptyError extends Error {
6
6
  constructor(dir) {
@@ -27,6 +27,7 @@ export async function createProject(input) {
27
27
  billing: input.billing ?? false,
28
28
  ui: input.ui ?? false,
29
29
  cli: input.cli ?? false,
30
+ mcp: input.mcp ?? false,
30
31
  };
31
32
  const dir = resolve(input.dir ?? input.name);
32
33
  const existing = await readdir(dir).catch(() => null);
@@ -45,6 +46,7 @@ export async function createProject(input) {
45
46
  'src/server.ts': serverTs(),
46
47
  'tests/app.test.ts': appTest(options),
47
48
  ...(options.cli ? { 'bin/basalt.ts': basaltBin() } : {}),
49
+ ...(options.mcp ? { '.mcp.json': mcpJson(options) } : {}),
48
50
  ...(options.ui ? uiFiles(options) : {}),
49
51
  };
50
52
  for (const [path, content] of Object.entries(files)) {
@@ -7,6 +7,8 @@ export interface ProjectOptions {
7
7
  ui: boolean;
8
8
  /** Scaffold the `basalt` CLI entrypoint (code generators + built-in commands). */
9
9
  cli: boolean;
10
+ /** Expose opted-in routes as MCP tools (@basaltkit/mcp) over HTTP at `/mcp`. */
11
+ mcp: boolean;
10
12
  }
11
13
  /** The dependency range for a @basalt package (override, else the base line). */
12
14
  export declare const versionOf: (pkg: string) => string;
@@ -20,6 +22,12 @@ export declare function serverTs(): string;
20
22
  export declare function basaltBin(): string;
21
23
  export declare function appTest(options: ProjectOptions): string;
22
24
  export declare function readme(options: ProjectOptions): string;
25
+ /**
26
+ * A Claude Code / generic MCP client config that registers the dev-only
27
+ * `basalt-ai-mcp` bridge for this project. Claude Code reads `.mcp.json` at the
28
+ * project root automatically; Claude Desktop uses the same server entry.
29
+ */
30
+ export declare function mcpJson(_options: ProjectOptions): string;
23
31
  export declare function gitignore(): string;
24
32
  /**
25
33
  * pnpm settings: esbuild's build script is required by tsx;
package/dist/templates.js CHANGED
@@ -5,7 +5,10 @@ const BASALT_VERSION = '^1.0.0';
5
5
  * package that advances past the base line needs its own range or a fresh app
6
6
  * can't install it. Add an entry when a package crosses a minor.
7
7
  */
8
- const VERSIONS = {};
8
+ const VERSIONS = {
9
+ // ai-mcp is on the 0.x line (published separately from the 1.x framework base).
10
+ '@basaltkit/ai-mcp': '^0.1.0',
11
+ };
9
12
  /** The dependency range for a @basalt package (override, else the base line). */
10
13
  export const versionOf = (pkg) => VERSIONS[pkg] ?? BASALT_VERSION;
11
14
  export function packageJson(options) {
@@ -24,6 +27,8 @@ export function packageJson(options) {
24
27
  dependencies['@basaltkit/auth'] = BASALT_VERSION;
25
28
  if (options.billing)
26
29
  dependencies['@basaltkit/subscriptions'] = BASALT_VERSION;
30
+ if (options.mcp)
31
+ dependencies['@basaltkit/mcp'] = BASALT_VERSION;
27
32
  if (options.cli) {
28
33
  // Runtime deps: app.ts uses commandsPlugin (@basaltkit/cli); prisma powers
29
34
  // prismaPlugin + `basalt prisma:sync`. @basaltkit/generator is dev-only (below).
@@ -50,6 +55,12 @@ export function packageJson(options) {
50
55
  // never does, so the app runs completely without the codegen/AI layer.
51
56
  devDependencies['@basaltkit/generator'] = versionOf('@basaltkit/generator');
52
57
  }
58
+ if (options.mcp) {
59
+ // Dev-only AI bridge: exposes analyze/plan/make to MCP clients (Claude
60
+ // Code/Desktop) via the `basalt-ai-mcp` bin. NEVER a runtime dependency — the
61
+ // app ships and runs without it (see `.mcp.json` for the client config).
62
+ devDependencies['@basaltkit/ai-mcp'] = versionOf('@basaltkit/ai-mcp');
63
+ }
53
64
  return `${JSON.stringify({
54
65
  name: options.name,
55
66
  version: '0.1.0',
@@ -166,7 +177,19 @@ export function appTs(options) {
166
177
  imports.push(`import { commandsPlugin, type CommandDefinition } from '@basaltkit/cli'`);
167
178
  plugins.push(`...(options.commands && options.commands.length > 0 ? [commandsPlugin(options.commands)] : [])`);
168
179
  }
169
- plugins.push(`fastifyPlugin({ routes: ${routesExpression} })`);
180
+ let adapterRoutesExpr = routesExpression;
181
+ if (options.mcp) {
182
+ imports.push(`import { mcpPlugin, mcpRoutes } from '@basaltkit/mcp'`);
183
+ // The MCP server scans the SAME app routes for `meta.mcp` (see routes.ts); the
184
+ // `/mcp` transport is added to the adapter so agents POST JSON-RPC to it. Tool
185
+ // calls run through the neutral pipeline, so tenancy/auth apply unchanged.
186
+ plugins.push(`mcpPlugin({ routes: ${routesExpression}, serverInfo: { name: '${options.name}', version: '0.1.0' } })`);
187
+ adapterRoutesExpr =
188
+ routesExpression === 'appRoutes'
189
+ ? '[...appRoutes, ...mcpRoutes()]'
190
+ : routesExpression.replace(/]$/, ', ...mcpRoutes()]');
191
+ }
192
+ plugins.push(`fastifyPlugin({ routes: ${adapterRoutesExpr} })`);
170
193
  const commandsField = options.cli
171
194
  ? `\n /** Dev/CLI commands (make:*, ai:*, prisma:sync) — passed ONLY by bin/basalt.ts. */\n commands?: CommandDefinition[]`
172
195
  : '';
@@ -201,6 +224,14 @@ export function routesTs(options) {
201
224
  ]
202
225
  : []),
203
226
  ];
227
+ // Opt these read-only routes in as MCP tools when MCP is enabled. Only safe,
228
+ // non-mutating endpoints are exposed — auth routes are deliberately left out.
229
+ const overviewMeta = options.mcp
230
+ ? `\n meta: { mcp: { name: 'overview', description: 'List what this API exposes: name, status and available endpoints.' } },`
231
+ : '';
232
+ const healthMeta = options.mcp
233
+ ? `\n meta: { mcp: { name: 'health', description: 'Liveness check — returns ok and the current request id.' } },`
234
+ : '';
204
235
  return `import { ctx } from '@basaltkit/core'
205
236
  import { route } from '@basaltkit/fastify'
206
237
 
@@ -208,7 +239,7 @@ export const appRoutes = [
208
239
  // Friendly index so \`GET /\` is never a bare 404 — lists what the API exposes.
209
240
  route({
210
241
  method: 'GET',
211
- url: '/',
242
+ url: '/',${overviewMeta}
212
243
  async handler() {
213
244
  return {
214
245
  name: '${options.name}',
@@ -219,7 +250,7 @@ export const appRoutes = [
219
250
  }),
220
251
  route({
221
252
  method: 'GET',
222
- url: '/health',
253
+ url: '/health',${healthMeta}
223
254
  async handler() {
224
255
  return { ok: true, requestId: ctx().requestId${options.tenancy ? ', tenant: ctx().tenant?.id ?? null' : ''} }
225
256
  },
@@ -313,6 +344,7 @@ export function readme(options) {
313
344
  ...(options.billing ? ['subscriptions with plans and feature limits'] : []),
314
345
  ...(options.ui ? ['web UI (React + shadcn/ui on @basaltkit/admin-shadcn + @basaltkit/sdk)'] : []),
315
346
  ...(options.cli ? ['`basalt` CLI with code generators (`make:*`) and built-in commands'] : []),
347
+ ...(options.mcp ? ['MCP server — read-only routes exposed as AI-agent tools at `/mcp`'] : []),
316
348
  ];
317
349
  return `# ${options.name}
318
350
 
@@ -352,9 +384,52 @@ pnpm --filter ${options.name}-web dev # terminal 2 — UI on http://localhost:
352
384
 
353
385
  Open <http://localhost:5180>. The Vite dev server proxies \`/api\` to the API,
354
386
  so there is no CORS to configure${options.auth ? '. Register, then sign in' : ''}.
387
+ `
388
+ : ''}${options.mcp
389
+ ? `
390
+ ## MCP server
391
+
392
+ Read-only routes marked with \`meta.mcp\` in \`src/routes.ts\` (the overview and
393
+ health endpoints) are exposed as [Model Context Protocol](https://modelcontextprotocol.io)
394
+ tools over HTTP at \`POST /mcp\`. Point any MCP client at it:
395
+
396
+ \`\`\`bash
397
+ # list the tools
398
+ curl -s localhost:3000/mcp -H 'content-type: application/json' \\
399
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
400
+ \`\`\`
401
+
402
+ Tool calls run through the same neutral pipeline as HTTP, so validation, tenancy
403
+ and auth apply unchanged. Add \`meta: { mcp: true }\` to any route to expose it —
404
+ keep mutations and auth flows off unless an agent should really call them.
405
+
406
+ ### AI dev tools
407
+
408
+ This project is also wired for AI-assisted development. \`.mcp.json\` registers the
409
+ **dev-only** \`basalt-ai-mcp\` bridge (a \`devDependency\` — never shipped in your
410
+ app's runtime) with MCP clients such as Claude Code and Claude Desktop. It exposes
411
+ \`basalt_analyze\`, \`basalt_doctor\`, \`basalt_plan\`, \`basalt_review\` and
412
+ \`basalt_make\` (preview by default, writes confined to this project) plus workflow
413
+ prompts. Claude Code picks up \`.mcp.json\` automatically; for Claude Desktop add the
414
+ same server to its config with \`--cwd=<absolute project path>\`.
355
415
  `
356
416
  : ''}`;
357
417
  }
418
+ /**
419
+ * A Claude Code / generic MCP client config that registers the dev-only
420
+ * `basalt-ai-mcp` bridge for this project. Claude Code reads `.mcp.json` at the
421
+ * project root automatically; Claude Desktop uses the same server entry.
422
+ */
423
+ export function mcpJson(_options) {
424
+ return `${JSON.stringify({
425
+ mcpServers: {
426
+ 'basalt-ai': {
427
+ command: 'npx',
428
+ args: ['-y', '@basaltkit/ai-mcp', '--cwd=.'],
429
+ },
430
+ },
431
+ }, null, 2)}\n`;
432
+ }
358
433
  export function gitignore() {
359
434
  return `node_modules/
360
435
  dist/
package/dist/wizard.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { PackageManager } from './index.js';
2
2
  import { type Choice, type Prompter } from './prompt.js';
3
- export type FeatureKey = 'tenancy' | 'auth' | 'billing' | 'ui' | 'cli';
3
+ export type FeatureKey = 'tenancy' | 'auth' | 'billing' | 'ui' | 'cli' | 'mcp';
4
4
  /** The feature toggles offered in the "custom" preset (and shown in the summary). */
5
5
  export declare const FEATURES: Choice<FeatureKey>[];
6
6
  interface Preset {
@@ -20,6 +20,7 @@ export interface WizardResult {
20
20
  billing: boolean;
21
21
  ui: boolean;
22
22
  cli: boolean;
23
+ mcp: boolean;
23
24
  pm: PackageManager;
24
25
  install: boolean;
25
26
  git: boolean;
package/dist/wizard.js CHANGED
@@ -6,11 +6,12 @@ export const FEATURES = [
6
6
  { value: 'billing', label: 'Subscriptions / billing', hint: 'Stripe, Paddle, Lemon Squeezy' },
7
7
  { value: 'ui', label: 'Web UI', hint: 'React + shadcn + SDK (pnpm workspace)' },
8
8
  { value: 'cli', label: 'basalt CLI', hint: 'code generators + commands' },
9
+ { value: 'mcp', label: 'MCP server', hint: 'expose routes as AI-agent tools (/mcp)' },
9
10
  ];
10
11
  export const PRESETS = [
11
12
  { value: 'saas', label: 'SaaS starter', hint: 'tenancy + auth + billing + CLI', features: ['tenancy', 'auth', 'billing', 'cli'] },
12
- { value: 'api', label: 'API only', hint: 'auth, no tenancy or UI', features: ['auth'] },
13
- { value: 'full', label: 'Full stack', hint: 'everything + web UI', features: ['tenancy', 'auth', 'billing', 'ui', 'cli'] },
13
+ { value: 'api', label: 'API only', hint: 'auth + MCP, no tenancy or UI', features: ['auth', 'mcp'] },
14
+ { value: 'full', label: 'Full stack', hint: 'everything + web UI', features: ['tenancy', 'auth', 'billing', 'ui', 'cli', 'mcp'] },
14
15
  { value: 'minimal', label: 'Minimal', hint: 'no batteries, add them later', features: [] },
15
16
  { value: 'custom', label: 'Custom', hint: 'pick features yourself', features: null },
16
17
  ];
@@ -88,6 +89,7 @@ export async function runWizard(prompter, options = {}) {
88
89
  billing: featureSet.has('billing'),
89
90
  ui: featureSet.has('ui'),
90
91
  cli: featureSet.has('cli'),
92
+ mcp: featureSet.has('mcp'),
91
93
  pm,
92
94
  install,
93
95
  git,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-basalt",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Scaffold a production-shaped Basalt SaaS app: npm create basalt my-saas [--billing] [--ui] [--cli] [--install] [--git]",
5
5
  "license": "MIT",
6
6
  "type": "module",