create-disposabl-app 0.1.1 → 0.1.2

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/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url";
8
8
  import { Command, CommanderError } from "commander";
9
9
  import * as p2 from "@clack/prompts";
10
10
  // package.json
11
- var version = "0.1.1";
11
+ var version = "0.1.2";
12
12
 
13
13
  // options.ts
14
14
  var platforms = [
@@ -36,8 +36,6 @@ const config = defineConfig(({ mode }) => {
36
36
  tailwindcss(),
37
37
  tanstackStart({
38
38
  router: kitRouter,
39
- // A separate mask keeps shell generation from replacing the prerendered home page.
40
- spa: { enabled: true, maskPath: "/?shell" },
41
39
  prerender: {
42
40
  enabled: true,
43
41
  autoStaticPathsDiscovery: true,
@@ -130,7 +130,7 @@
130
130
  },
131
131
  "packages/create-disposabl-app": {
132
132
  "name": "create-disposabl-app",
133
- "version": "0.1.1",
133
+ "version": "0.1.2",
134
134
  "bin": {
135
135
  "create-disposabl-app": "dist/index.js",
136
136
  },
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "version": "0.1.0",
3
- "platforms": ["web", "mcp"],
4
- "features": ["authentication"]
3
+ "platforms": [
4
+ "web"
5
+ ],
6
+ "features": [
7
+ "authentication"
8
+ ]
5
9
  }
@@ -23,3 +23,11 @@ npm create convex@latest -t bare
23
23
  App drafts require `website`; `cli`, `typescript`, and `python` require `api`, and `openai` and `claude` require `mcp`. App lists currently return at most 100 records. Existing records without platforms read as `website`.
24
24
 
25
25
  Boat requires `BOAT_API_KEY` in the Convex deployment. Set `SANDBOX_OPENAI_API_KEY` to a separately scoped, budget-limited OpenAI API key before sending a message; it is exposed to code inside the sandbox. Creation and resume use `noEnv: true`. Codex credentials are written to protected files in its sandbox home, and healthy running sandboxes are reused without stopping them. The user-scoped MCP key is revoked on completion or after one hour, so long-running prompts can lose MCP access. An uncertain prompt submission stays locked for review instead of being retried automatically. Live sandbox provisioning, Codex credential loading, and dispatch have not been verified.
26
+
27
+ # MCP tools
28
+
29
+ Run `bun run dev` at the workspace root to start Turbo, or in this directory to start only the backend with `convex dev`.
30
+
31
+ The function builders still accept `platforms: true`, per-platform flags, and the legacy `mcp` flag. MCP server creation is still controlled by `disposabl.json`.
32
+
33
+ Tool discovery and platform generation are removed for now. The MCP server receives `createMcpTools({})`, so its tool list stays empty even when MCP is enabled and internal functions have `platforms: true`. The tool adapter remains available for future use.
@@ -54,7 +54,6 @@
54
54
  "./client/hooks/*": "./client/hooks/*.ts",
55
55
  "./server/http": "./server/http.ts",
56
56
  "./server/lib/functions": "./server/lib/functions.ts",
57
- "./server/lib/static-routes": "./server/lib/static-routes.ts",
58
57
  "./client/vite.config": "./client/vite.config.ts"
59
58
  }
60
59
  }
@@ -1,86 +1,42 @@
1
1
  import { getAuthUserId } from "@convex-dev/auth/server";
2
- import { McpServer, tool } from "@convex-dev/mcp";
3
- import { registerStaticRoutes } from "./lib/static-routes";
2
+ import { McpServer } from "@convex-dev/mcp";
3
+ import { registerStaticRoutes } from "@convex-dev/static-hosting";
4
4
  import { httpRouter } from "convex/server";
5
- import { v } from "convex/values";
6
- import config from "../../../disposabl.json";
7
- import { components, internal } from "../../backend/convex/_generated/api";
8
- import type { Doc, Id } from "../../backend/convex/_generated/dataModel";
5
+ import { components } from "../../backend/convex/_generated/api";
9
6
  import { env } from "../../backend/convex/_generated/server";
10
- import schema from "../../backend/convex/schema";
7
+ import { createMcpTools } from "./lib/mcp-tools";
11
8
  import { getPlatforms } from "./lib/platforms";
12
9
 
13
10
  export { tool } from "@convex-dev/mcp";
14
11
  export type { McpServerOptions, McpUser, ToolCtx } from "@convex-dev/mcp";
15
12
 
16
- const mcp = new McpServer(components.mcp, {
17
- name: "disposabl",
18
- title: "Disposabl",
19
- version: config.version,
20
- siteUrl: env.CONVEX_SITE_URL,
21
- consentUrl: `${env.CONVEX_SITE_URL}/consent`,
22
- scopes: {
23
- "todos:read": "Read your todos.",
24
- "todos:write": "Create todos for your account.",
25
- },
26
- tools: {
27
- create_todo: tool({
28
- description: "Create a todo for the signed-in user.",
29
- args: { title: v.string() },
30
- returns: v.id("todos"),
31
- annotations: { destructiveHint: false, openWorldHint: false },
32
- scope: "todos:write",
33
- handler: async (ctx, args, user): Promise<Id<"todos">> =>
34
- await ctx.runMutation(internal.todos.internal.create, {
35
- ...args,
36
- userId: user.userId,
37
- }),
38
- }),
39
- list_todos: tool({
40
- description: "List the signed-in user's 100 most recent todos.",
41
- args: {},
42
- returns: v.array(schema.doc("todos")),
43
- annotations: {
44
- readOnlyHint: true,
45
- idempotentHint: true,
46
- openWorldHint: false,
47
- },
48
- scope: "todos:read",
49
- handler: async (ctx, _args, user): Promise<Doc<"todos">[]> =>
50
- await ctx.runQuery(internal.todos.internal.list, {
51
- userId: user.userId,
52
- }),
53
- }),
54
- get_todo: tool({
55
- description: "Get one of the signed-in user's todos by ID.",
56
- args: { id: v.id("todos") },
57
- returns: v.union(schema.doc("todos"), v.null()),
58
- annotations: {
59
- readOnlyHint: true,
60
- idempotentHint: true,
61
- openWorldHint: false,
62
- },
63
- scope: "todos:read",
64
- handler: async (ctx, args, user): Promise<Doc<"todos"> | null> =>
65
- await ctx.runQuery(internal.todos.internal.get, {
66
- ...args,
67
- userId: user.userId,
68
- }),
69
- }),
70
- },
71
- });
13
+ const mcp = getPlatforms().isMcpEnabled
14
+ ? new McpServer(components.mcp, {
15
+ name: "disposabl",
16
+ title: "Disposabl",
17
+ version: "1.0.0",
18
+ siteUrl: env.CONVEX_SITE_URL,
19
+ consentUrl: `${env.CONVEX_SITE_URL}/consent`,
20
+ scopes: {},
21
+ // Discovery is disabled; platform flags do not register tools yet.
22
+ tools: createMcpTools({}),
23
+ })
24
+ : undefined;
72
25
 
26
+ // Keep the enabled API types for consumers; disabled exports are absent at runtime.
73
27
  export const {
74
28
  getAuthRequest,
75
29
  authorize,
76
30
  listConnections,
77
31
  revokeConnection,
78
32
  createApiKey,
79
- } = mcp.api({ getUserId: getAuthUserId });
33
+ } =
34
+ mcp?.api({ getUserId: getAuthUserId }) ??
35
+ ({} as ReturnType<McpServer["api"]>);
80
36
 
81
37
  export default function createHttpRouter() {
82
38
  const http = httpRouter();
83
- if (getPlatforms().isMcpEnabled) mcp.registerRoutes(http);
39
+ mcp?.registerRoutes(http);
84
40
  registerStaticRoutes(http, components.staticHosting);
85
41
  return http;
86
42
  }
@@ -26,9 +26,15 @@ const withPlatforms = <TDefinition, TResult extends object>(
26
26
  mcp?: boolean;
27
27
  },
28
28
  ): TResult => {
29
+ const mcpEnabled =
30
+ definition.platforms === undefined
31
+ ? definition.mcp === true
32
+ : definition.platforms === true ||
33
+ (typeof definition.platforms === "object" &&
34
+ definition.platforms.mcp === true);
29
35
  return Object.assign(builder(definition), {
30
36
  platforms: definition.platforms,
31
- mcp: definition.mcp ?? definition.platforms?.mcp ?? false,
37
+ mcp: mcpEnabled ? (definition.mcp ?? true) : false,
32
38
  args: definition.args,
33
39
  returns: definition.returns,
34
40
  });
@@ -0,0 +1,47 @@
1
+ import { tool, type ToolDefinition } from "@convex-dev/mcp";
2
+ import { makeFunctionReference as reference } from "convex/server";
3
+ import type { GenericValidator, PropertyValidators } from "convex/values";
4
+
5
+ type PlatformFunction = {
6
+ isInternal?: boolean;
7
+ isQuery?: boolean;
8
+ isMutation?: boolean;
9
+ mcp?: boolean;
10
+ args: PropertyValidators;
11
+ returns: GenericValidator;
12
+ };
13
+
14
+ /** Adapt opted-in internal functions without duplicating their validators. */
15
+ export function createMcpTools(
16
+ modules: Record<string, Record<string, unknown>>,
17
+ ) {
18
+ const tools: Record<string, ToolDefinition> = Object.create(null);
19
+ for (const [path, exports] of Object.entries(modules)) {
20
+ for (const [exportName, value] of Object.entries(exports)) {
21
+ const fn = value as PlatformFunction | null;
22
+ if (!fn?.isInternal || !fn.mcp) continue;
23
+ const id = `${path}:${exportName}`;
24
+ const name = id.replace(/[^A-Za-z0-9_.-]/g, "_");
25
+ if (Object.hasOwn(tools, name))
26
+ throw new Error(`Duplicate MCP tool name: ${name}`);
27
+ const { userId, ...args } = fn.args;
28
+ tools[name] = tool({
29
+ description: `Call ${id}.`,
30
+ ...(fn.isQuery ? { annotations: { readOnlyHint: true } } : {}),
31
+ args,
32
+ returns: fn.returns,
33
+ handler: async (ctx, input, user) => {
34
+ const params = {
35
+ ...input,
36
+ ...(userId ? { userId: user.userId } : {}),
37
+ };
38
+ if (fn.isQuery) return ctx.runQuery(reference<"query">(id), params);
39
+ if (fn.isMutation)
40
+ return ctx.runMutation(reference<"mutation">(id), params);
41
+ return ctx.runAction(reference<"action">(id), params);
42
+ },
43
+ });
44
+ }
45
+ }
46
+ return tools;
47
+ }
@@ -1,6 +1,6 @@
1
1
  import config from "../../../../disposabl.json";
2
2
 
3
- export type Platforms = { api?: boolean; mcp?: boolean };
3
+ export type Platforms = boolean | { api?: boolean; mcp?: boolean };
4
4
 
5
5
  /** The generated app config is the source of truth for enabled transports. */
6
6
  export function getPlatforms() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-disposabl-app",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Create a Disposabl app",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,56 +0,0 @@
1
- import type { ComponentApi } from "@convex-dev/static-hosting/_generated/component";
2
- import { httpActionGeneric, type HttpRouter } from "convex/server";
3
-
4
- /** Serve build output by convention, without a second list of application routes. */
5
- export function registerStaticRoutes(
6
- http: HttpRouter,
7
- component: ComponentApi,
8
- ) {
9
- const handler = httpActionGeneric(async (ctx, request) => {
10
- let pathname: string;
11
- try {
12
- pathname = decodeURIComponent(new URL(request.url).pathname);
13
- } catch {
14
- return new Response("Bad Request", { status: 400 });
15
- }
16
-
17
- const resolve = (path: string) =>
18
- ctx.runQuery(component.lib.resolveAssetForHttp, {
19
- path,
20
- spaFallback: false,
21
- });
22
- const path = pathname === "/" ? "/index.html" : pathname;
23
- let asset = await resolve(path);
24
- let shell = path === "/_shell.html";
25
- if (!asset && !pathname.split("/").filter(Boolean).at(-1)?.includes(".")) {
26
- asset = await resolve(`${pathname.replace(/\/$/, "")}/index.html`);
27
- if (!asset) {
28
- asset = await resolve("/_shell.html");
29
- shell = true;
30
- }
31
- }
32
- if (!asset) return new Response("Not Found", { status: 404 });
33
-
34
- const html = asset.contentType.startsWith("text/html");
35
- const headers = {
36
- "Content-Type": asset.contentType,
37
- "Cache-Control": html ? "no-store" : "public, max-age=0, must-revalidate",
38
- "X-Content-Type-Options": "nosniff",
39
- ...(shell ? { "X-Robots-Tag": "noindex, nofollow" } : {}),
40
- };
41
- if (asset.blobId && !html) {
42
- return new Response(null, {
43
- status: 302,
44
- headers: { ...headers, Location: `/fs/blobs/${asset.blobId}` },
45
- });
46
- }
47
- if (!asset.storageUrl) return new Response("Not Found", { status: 404 });
48
- const response = await fetch(asset.storageUrl, {
49
- signal: AbortSignal.timeout(15_000),
50
- });
51
- if (!response.ok) return new Response("Storage error", { status: 502 });
52
- return new Response(response.body, { headers });
53
- });
54
-
55
- http.route({ pathPrefix: "/", method: "GET", handler });
56
- }