@waniwani/kit 0.1.1 → 0.1.4

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/cli/validate.mjs CHANGED
@@ -8,9 +8,19 @@
8
8
 
9
9
  import { readFileSync } from "node:fs";
10
10
  import { relative } from "node:path";
11
+ import { loadAppEnv } from "./env.mjs";
11
12
 
12
13
  const NAME_RE = /^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$/;
13
14
 
15
+ /**
16
+ * What may appear in an endpoint path segment. Deliberately narrower than what
17
+ * a filesystem allows: the segment becomes a URL path, and a file called
18
+ * `Book Call.ts` would be served at a URL nobody would guess.
19
+ */
20
+ const SEGMENT_RE = /^[a-zA-Z0-9._-]+$/;
21
+
22
+ const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "options"]);
23
+
14
24
  class Report {
15
25
  constructor(root) {
16
26
  this.root = root;
@@ -47,11 +57,11 @@ function checkStructure(app, report) {
47
57
  );
48
58
  }
49
59
 
50
- if (app.tools.length + app.widgets.length + app.flows.length + app.docs.length === 0) {
60
+ if (app.tools.length + app.widgets.length + app.flows.length === 0) {
51
61
  report.error(
52
62
  ".",
53
63
  "this app exposes nothing",
54
- "add a tool (tools/<name>.ts), a widget (widgets/<name>/), a flow (flows/<name>.ts), or docs (docs/<slug>.md)",
64
+ "add a tool (tools/<name>.ts), a widget (widgets/<name>/), or a flow (flows/<name>.ts)",
55
65
  );
56
66
  }
57
67
 
@@ -113,6 +123,49 @@ function checkStructure(app, report) {
113
123
  seen.set(entry.name, entry);
114
124
  }
115
125
 
126
+ // An endpoint's file position is its URL, so a segment that cannot appear in
127
+ // a URL is a file served somewhere unguessable, and two files resolving to
128
+ // one path means the second mount is dead — Express answers from the first.
129
+ const paths = new Map();
130
+ // The generator names one import per endpoint, camel-cased from the path, so
131
+ // two paths that camel-case alike (`api/cal-slots.ts`, `api/cal/slots.ts`)
132
+ // would emit the same identifier twice and fail in generated code the author
133
+ // cannot open.
134
+ const identifiers = new Map();
135
+ for (const endpoint of app.endpoints) {
136
+ const where = rel(root, endpoint.file);
137
+
138
+ const identifier = endpoint.segments.join("-").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
139
+ const clash = identifiers.get(identifier);
140
+ if (clash) {
141
+ report.error(
142
+ where,
143
+ `this path generates the same import name as ${clash}`,
144
+ "rename one of the two — the generator derives an identifier from the path",
145
+ );
146
+ }
147
+ identifiers.set(identifier, where);
148
+
149
+ for (const segment of endpoint.segments) {
150
+ if (SEGMENT_RE.test(segment)) continue;
151
+ report.error(
152
+ where,
153
+ `"${segment}" cannot be part of a URL path`,
154
+ "use letters, digits, dashes, dots and underscores — the file's position is the endpoint's path",
155
+ );
156
+ }
157
+
158
+ const previous = paths.get(endpoint.path);
159
+ if (previous) {
160
+ report.error(
161
+ where,
162
+ `${endpoint.path} is already served by ${previous}`,
163
+ "two files resolve to one path — Express answers from the first, so this one never runs",
164
+ );
165
+ }
166
+ paths.set(endpoint.path, where);
167
+ }
168
+
116
169
  // Flows point at widgets by name. Catching a typo here beats catching it
117
170
  // when a user is halfway through a conversation.
118
171
  const widgetNames = new Set(app.widgets.map((w) => w.name));
@@ -188,6 +241,27 @@ async function checkModules(app, report) {
188
241
  }
189
242
  }
190
243
 
244
+ for (const endpoint of app.endpoints) {
245
+ const where = rel(root, endpoint.file);
246
+ const def = await load(endpoint.file, where, report);
247
+ if (!def) continue;
248
+ if (typeof def.handler !== "function") {
249
+ report.error(
250
+ where,
251
+ "endpoint is missing handler()",
252
+ "export default defineEndpoint({ handler: (req, res) => { ... } })",
253
+ );
254
+ }
255
+ for (const method of def.method ? [def.method].flat() : []) {
256
+ if (HTTP_METHODS.has(method)) continue;
257
+ report.error(
258
+ where,
259
+ `"${method}" is not an HTTP method`,
260
+ `one of: ${[...HTTP_METHODS].join(", ")}`,
261
+ );
262
+ }
263
+ }
264
+
191
265
  for (const flow of app.flows) {
192
266
  const where = rel(root, flow.file);
193
267
  const def = await load(flow.file, where, report);
@@ -238,6 +312,11 @@ async function load(file, where, report) {
238
312
  }
239
313
 
240
314
  export async function validateApp(app) {
315
+ // The check imports every server-safe module for real, and a module that
316
+ // builds a client at import time reads the environment while doing it. An app
317
+ // that runs fine would otherwise fail its own build check over a variable
318
+ // sitting in the file next to it.
319
+ loadAppEnv(app.root);
241
320
  const report = new Report(app.root);
242
321
  checkStructure(app, report);
243
322
  // Importing broken modules produces noise on top of structural errors.
package/dist/index.d.ts CHANGED
@@ -12,8 +12,9 @@
12
12
  * widgets/<name>/widget.ts export default defineWidget({ ... })
13
13
  * widgets/<name>/ui.tsx export default function Component() { ... }
14
14
  * flows/<name>.ts export default createFlow({ ... }).compile()
15
- * docs/<slug>.md searchable knowledge
15
+ * api/<path>.ts export default defineEndpoint({ ... })
16
16
  */
17
+ import type { RequestHandler } from "express";
17
18
  import type { z } from "zod";
18
19
  /** A Zod object shape — `{ name: z.string() }`, not `z.object({ ... })`. */
19
20
  export type Shape = z.ZodRawShape;
@@ -104,10 +105,43 @@ export type WidgetDefinition<S extends Shape = Shape> = {
104
105
  load?: (input: Infer<S>) => Infer<S> | Promise<Infer<S>>;
105
106
  };
106
107
  export declare function defineWidget<S extends Shape>(def: WidgetDefinition<S>): WidgetDefinition<S>;
107
- /** A `docs/<slug>.md` file, parsed at build time and inlined into the bundle. */
108
- export type DocEntry = {
109
- slug: string;
110
- title: string;
111
- body: string;
108
+ /**
109
+ * HTTP methods an endpoint can be restricted to. `undefined` accepts every
110
+ * method, which is what an Express `use()` mount does.
111
+ */
112
+ export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "head" | "options";
113
+ /**
114
+ * A plain HTTP endpoint served by the same server as the MCP tools.
115
+ *
116
+ * This exists for the browser, not for the model. A widget runs in a
117
+ * cross-origin iframe and can `fetch()` its own server at
118
+ * `window.skybridge.serverUrl` — for a booking, a price lookup, a webhook
119
+ * receiver — and the model never sees the call. Anything the *model* should be
120
+ * able to reach belongs in `tools/`, not here.
121
+ *
122
+ * The path comes from the file's location, `/api` prefix included:
123
+ * `api/cal/slots.ts` is served at `/api/cal/slots`.
124
+ */
125
+ export type EndpointDefinition = {
126
+ /**
127
+ * Restrict the endpoint to these methods, answering anything else with 405.
128
+ * Defaults to accepting every method.
129
+ */
130
+ method?: HttpMethod | HttpMethod[];
131
+ /**
132
+ * Answer cross-origin requests, preflight included. On by default: a widget
133
+ * is served from a different origin than the server it calls, so an endpoint
134
+ * without CORS is one the widget cannot reach.
135
+ */
136
+ cors?: boolean;
137
+ /**
138
+ * Parse a JSON request body into `req.body`. On by default — the framework
139
+ * installs no body parser of its own, so an endpoint without this reads
140
+ * `req.body` as `undefined`.
141
+ */
142
+ json?: boolean;
143
+ /** An Express handler. Throwing is safe: the runtime answers 500 and logs. */
144
+ handler: RequestHandler;
112
145
  };
146
+ export declare function defineEndpoint(def: EndpointDefinition): EndpointDefinition;
113
147
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * widgets/<name>/widget.ts export default defineWidget({ ... })
13
13
  * widgets/<name>/ui.tsx export default function Component() { ... }
14
14
  * flows/<name>.ts export default createFlow({ ... }).compile()
15
- * docs/<slug>.md searchable knowledge
15
+ * api/<path>.ts export default defineEndpoint({ ... })
16
16
  */
17
17
  export function defineApp(config) {
18
18
  return config;
@@ -23,4 +23,7 @@ export function defineTool(def) {
23
23
  export function defineWidget(def) {
24
24
  return def;
25
25
  }
26
+ export function defineEndpoint(def) {
27
+ return def;
28
+ }
26
29
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA0CH,MAAM,UAAU,SAAS,CAAC,MAAiB;IAC1C,OAAO,MAAM,CAAC;AACf,CAAC;AAwBD,MAAM,UAAU,UAAU,CACzB,GAAyB;IAEzB,OAAO,GAAG,CAAC;AACZ,CAAC;AAuCD,MAAM,UAAU,YAAY,CAAkB,GAAwB;IACrE,OAAO,GAAG,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA2CH,MAAM,UAAU,SAAS,CAAC,MAAiB;IAC1C,OAAO,MAAM,CAAC;AACf,CAAC;AAwBD,MAAM,UAAU,UAAU,CACzB,GAAyB;IAEzB,OAAO,GAAG,CAAC;AACZ,CAAC;AAuCD,MAAM,UAAU,YAAY,CAAkB,GAAwB;IACrE,OAAO,GAAG,CAAC;AACZ,CAAC;AA4CD,MAAM,UAAU,cAAc,CAAC,GAAuB;IACrD,OAAO,GAAG,CAAC;AACZ,CAAC"}
package/dist/server.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * hands them to `registerApp()`. Nothing else.
12
12
  */
13
13
  import { McpServer } from "skybridge/server";
14
- import type { DocEntry, Shape, ToolHints, WidgetCsp } from "./index.js";
14
+ import type { EndpointDefinition, Shape, ToolHints, WidgetCsp } from "./index.js";
15
15
  /**
16
16
  * The manifest holds definitions with unrelated schemas side by side, so the
17
17
  * handler signatures are widened here. `never` in the parameter position
@@ -50,7 +50,11 @@ export type Manifest = {
50
50
  def: AnyWidgetDefinition;
51
51
  }>;
52
52
  flows: CompiledFlow[];
53
- docs: DocEntry[];
53
+ /** HTTP endpoints, each with the path its file position produced. */
54
+ endpoints?: Array<{
55
+ path: string;
56
+ def: EndpointDefinition;
57
+ }>;
54
58
  /**
55
59
  * Origins the template's Tailwind entry loads from, read off it at build
56
60
  * time. Every view imports that stylesheet, so every widget needs them.
@@ -58,8 +62,7 @@ export type Manifest = {
58
62
  styleDomains?: string[];
59
63
  };
60
64
  /**
61
- * Register an app's tools, widgets, flows, and docs onto a server the template
62
- * built.
65
+ * Register an app's tools, widgets and flows onto a server the template built.
63
66
  *
64
67
  * The template owns construction, its own tools, `withWaniwani`, and `run()`.
65
68
  * This adds to that server rather than replacing it, so a tool the template
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAiB,MAAM,kBAAkB,CAAC;AAE5D,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExE;;;;GAIG;AACH,KAAK,iBAAiB,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAC/B,CAAC;AAEF,KAAK,mBAAmB,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CACjC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IAEb,MAAM,EAAE,GAAG,CAAC;IAEZ,OAAO,EAAE,GAAG,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACtB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,iBAAiB,CAAA;KAAE,CAAC,CAAC;IACvD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IAC3D,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAyHF;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CA8F3F"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAE,SAAS,EAAiB,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,kBAAkB,EAAc,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9F;;;;GAIG;AACH,KAAK,iBAAiB,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAC/B,CAAC;AAEF,KAAK,mBAAmB,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CACjC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IAEb,MAAM,EAAE,GAAG,CAAC;IAEZ,OAAO,EAAE,GAAG,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACtB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,iBAAiB,CAAA;KAAE,CAAC,CAAC;IACvD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IAC3D,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,qEAAqE;IACrE,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,kBAAkB,CAAA;KAAE,CAAC,CAAC;IAC7D;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AA6IF;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAgG3F"}
package/dist/server.js CHANGED
@@ -10,8 +10,9 @@
10
10
  * against that seam — `src/waniwani.ts` — which imports the app's modules and
11
11
  * hands them to `registerApp()`. Nothing else.
12
12
  */
13
+ import cors from "cors";
14
+ import express, {} from "express";
13
15
  import { McpServer } from "skybridge/server";
14
- import { z } from "zod";
15
16
  /**
16
17
  * The origins a widget may load assets from: its own, plus the ones its
17
18
  * stylesheet needs.
@@ -74,52 +75,76 @@ function widgetError(name, error) {
74
75
  ],
75
76
  };
76
77
  }
77
- function registerDocsTool(server, docs) {
78
- const corpus = docs.map((doc) => ({
79
- ...doc,
80
- haystack: `${doc.title}\n${doc.body}`.toLowerCase(),
81
- }));
82
- server.registerTool({
83
- name: "search_docs",
84
- title: "Search the documentation",
85
- description: "Search this product's documentation and answer general questions — pricing, eligibility, policies, how things work. Always search before answering, and answer only from what comes back. Never invent facts that are not in the results.",
86
- inputSchema: { question: z.string().describe("The user's question, in their own words.") },
87
- outputSchema: {
88
- results: z.array(z.object({ slug: z.string(), title: z.string(), body: z.string() })),
89
- },
90
- annotations: annotations("Search the documentation", undefined, { readOnly: true }),
91
- }, async ({ question }) => {
92
- const terms = question
93
- .toLowerCase()
94
- .split(/[^a-z0-9]+/)
95
- .filter((term) => term.length > 2);
96
- const results = corpus
97
- .map((doc) => ({
98
- doc,
99
- score: terms.reduce((sum, term) => sum + (doc.haystack.includes(term) ? 1 : 0), 0),
100
- }))
101
- .filter(({ score }) => score > 0)
102
- .sort((a, b) => b.score - a.score)
103
- .slice(0, 3)
104
- .map(({ doc }) => ({ slug: doc.slug, title: doc.title, body: doc.body }));
105
- if (results.length === 0) {
106
- const text = "Nothing in the documentation covers that question.";
107
- return { structuredContent: { results: [] }, content: [{ type: "text", text }] };
108
- }
109
- return {
110
- structuredContent: { results },
111
- content: [
112
- {
113
- type: "text",
114
- text: results.map((r) => `## ${r.title}\n${r.body}`).join("\n\n---\n\n"),
115
- },
116
- ],
117
- };
118
- });
78
+ /**
79
+ * Answer anything outside the declared methods with 405 rather than running the
80
+ * handler. An endpoint is mounted with `use()`, which matches every method, so
81
+ * without this a `GET /api/cal/book` would reach a handler written for a POST
82
+ * body and fail somewhere less obvious.
83
+ */
84
+ function methodGuard(allowed) {
85
+ // A preflight never carries the real method, and answering it 405 blocks the
86
+ // request it was asking about.
87
+ const pass = new Set([...allowed, "OPTIONS"]);
88
+ return (req, res, next) => {
89
+ if (pass.has(req.method))
90
+ return next();
91
+ res.setHeader("Allow", allowed.join(", "));
92
+ res.status(405).json({ error: `${req.method} not allowed` });
93
+ };
94
+ }
95
+ /**
96
+ * The last link in every endpoint's chain: a 4-arity middleware, which is how
97
+ * Express recognises an error handler.
98
+ *
99
+ * Scoped to the endpoint's own mount path rather than the app, so it cannot
100
+ * catch anything the endpoint did not cause. Without it a rejected handler — or
101
+ * a malformed JSON body, which the parser reports the same way — reaches
102
+ * Express's default handler and answers an HTML error page to a `fetch()` that
103
+ * is waiting for JSON.
104
+ */
105
+ function endpointErrorHandler(path) {
106
+ return (error, _req, res, next) => {
107
+ console.error(`[waniwani] endpoint "${path}" failed:`, error);
108
+ if (res.headersSent)
109
+ return next(error);
110
+ // `express.json()` rejects a malformed body with a 400 already on the error.
111
+ const status = typeof error?.status === "number"
112
+ ? error.status
113
+ : 500;
114
+ res.status(status).json({
115
+ error: error instanceof Error ? error.message : "Internal server error",
116
+ });
117
+ };
118
+ }
119
+ /**
120
+ * Mount an app's HTTP endpoints on the server's Express app.
121
+ *
122
+ * Everything the framework does not do for us is done here, once, rather than
123
+ * left to each endpoint to remember: CORS (a widget calls from another origin),
124
+ * a JSON body parser (the framework installs none, so `req.body` would be
125
+ * `undefined`), the method guard, and the error envelope.
126
+ */
127
+ function registerEndpoints(server, endpoints) {
128
+ for (const { path, def } of endpoints) {
129
+ const methods = def.method ? [def.method].flat().map((m) => m.toUpperCase()) : undefined;
130
+ const chain = [];
131
+ // The preflight answer names the methods the guard below actually accepts.
132
+ // Browsers cache it, so advertising a method that then answers 405 is a
133
+ // contradiction the widget author has to debug twice.
134
+ if (def.cors !== false)
135
+ chain.push(cors(methods ? { methods } : undefined));
136
+ if (def.json !== false)
137
+ chain.push(express.json());
138
+ if (methods)
139
+ chain.push(methodGuard(methods));
140
+ chain.push(def.handler, endpointErrorHandler(path));
141
+ // The error handler's arity is what makes Express treat it as one, and
142
+ // `use()` is typed for request handlers only.
143
+ server.use(path, ...chain);
144
+ }
119
145
  }
120
146
  /**
121
- * Register an app's tools, widgets, flows, and docs onto a server the template
122
- * built.
147
+ * Register an app's tools, widgets and flows onto a server the template built.
123
148
  *
124
149
  * The template owns construction, its own tools, `withWaniwani`, and `run()`.
125
150
  * This adds to that server rather than replacing it, so a tool the template
@@ -127,7 +152,12 @@ function registerDocsTool(server, docs) {
127
152
  * app's own tools sit alongside it.
128
153
  */
129
154
  export async function registerApp(server, manifest) {
130
- const { tools, widgets, flows, docs, styleDomains = [] } = manifest;
155
+ const { tools, widgets, flows, endpoints = [], styleDomains = [] } = manifest;
156
+ // Before the tools, because Express matches in registration order and the
157
+ // framework mounts `/mcp` after this function returns. Nothing here can
158
+ // shadow it — `/api/...` and `/mcp` do not overlap — but the ordering is the
159
+ // reason an endpoint is reachable at all.
160
+ registerEndpoints(server, endpoints);
131
161
  // Widgets: one `data` schema drives the input schema, the structured output,
132
162
  // and the type the component receives.
133
163
  //
@@ -201,9 +231,6 @@ export async function registerApp(server, manifest) {
201
231
  for (const flow of flows) {
202
232
  server.registerTool({ ...flow.config, name: flow.name }, flow.handler);
203
233
  }
204
- if (docs.length > 0) {
205
- registerDocsTool(server, docs);
206
- }
207
234
  // `withWaniwani` is deliberately not called here. It wraps every registered
208
235
  // handler in place, so it has to run after the last registration — which is
209
236
  // the template's, not this function's. `src/server.ts` calls it.
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@waniwani/kit",
3
- "version": "0.1.1",
4
- "description": "Build an MCP app as a folder: tools, widgets, flows and docs, with one CLI and one shared server runtime.",
3
+ "version": "0.1.4",
4
+ "description": "Build an MCP app as a folder: tools, widgets and flows, with one CLI and one shared server runtime.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "waniwani": "cli/index.mjs"
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "//files": "`src` ships alongside `dist` on purpose: `waniwani eject` vendors the runtime as readable TypeScript, and it reads it out of the installed package.",
26
26
  "//dependencies": "tsx, typescript and the @types packages are here rather than in devDependencies because the underlying framework shells out to tsc and tsx by bare name and resolves types from the app repo tree, while declaring none of them. An app repo owns no build config, so this package is the only thing that can put them there. nodemon is the fourth of that set and arrives on its own, as a peer of it.",
27
+ "//express": "express and cors back the api/ convention: the runtime mounts each endpoint with a JSON body parser and CORS, and an app's handlers are typed against express. Both are already skybridge's own dependencies at these ranges, so declaring them here adds no second copy — it stops an app from depending on a transitive hoist.",
27
28
  "files": [
28
29
  "dist",
29
30
  "src",
@@ -41,12 +42,16 @@
41
42
  "dependencies": {
42
43
  "@modelcontextprotocol/sdk": "^1.29.0",
43
44
  "@tailwindcss/vite": "^4.3.3",
45
+ "@types/cors": "^2.8.19",
46
+ "@types/express": "^5.0.6",
44
47
  "@types/node": "^24.12.0",
45
48
  "@types/react": "^19.2.14",
46
49
  "@types/react-dom": "^19.2.3",
47
50
  "@vitejs/plugin-react": "^6.0.3",
48
51
  "@waniwani/sdk": "^0.19.5",
52
+ "cors": "^2.8.6",
49
53
  "dotenv": "^17.4.1",
54
+ "express": "^5.2.1",
50
55
  "skybridge": "^1.3.5",
51
56
  "tailwindcss": "^4.3.3",
52
57
  "tsx": "^4.20.6",
@@ -56,10 +61,6 @@
56
61
  "devDependencies": {
57
62
  "@skybridge/devtools": "^1.2.7"
58
63
  },
59
- "//optionalDependencies": "cloudflared is what `waniwani tunnel` runs, and its install step fetches a platform binary. Every other command works without it, so a CI image or a container build installing with --omit=optional skips the download and still gets a working CLI.",
60
- "optionalDependencies": {
61
- "cloudflared": "^0.7.1"
62
- },
63
64
  "peerDependencies": {
64
65
  "react": ">=19",
65
66
  "react-dom": ">=19",
package/src/index.ts CHANGED
@@ -12,9 +12,10 @@
12
12
  * widgets/<name>/widget.ts export default defineWidget({ ... })
13
13
  * widgets/<name>/ui.tsx export default function Component() { ... }
14
14
  * flows/<name>.ts export default createFlow({ ... }).compile()
15
- * docs/<slug>.md searchable knowledge
15
+ * api/<path>.ts export default defineEndpoint({ ... })
16
16
  */
17
17
 
18
+ import type { RequestHandler } from "express";
18
19
  import type { z } from "zod";
19
20
 
20
21
  /** A Zod object shape — `{ name: z.string() }`, not `z.object({ ... })`. */
@@ -128,11 +129,48 @@ export function defineWidget<S extends Shape>(def: WidgetDefinition<S>): WidgetD
128
129
  return def;
129
130
  }
130
131
 
131
- // ----------------------------------------------------------------------- docs
132
+ // ------------------------------------------------------------------ endpoints
132
133
 
133
- /** A `docs/<slug>.md` file, parsed at build time and inlined into the bundle. */
134
- export type DocEntry = {
135
- slug: string;
136
- title: string;
137
- body: string;
134
+ /**
135
+ * HTTP methods an endpoint can be restricted to. `undefined` accepts every
136
+ * method, which is what an Express `use()` mount does.
137
+ */
138
+ export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "head" | "options";
139
+
140
+ /**
141
+ * A plain HTTP endpoint served by the same server as the MCP tools.
142
+ *
143
+ * This exists for the browser, not for the model. A widget runs in a
144
+ * cross-origin iframe and can `fetch()` its own server at
145
+ * `window.skybridge.serverUrl` — for a booking, a price lookup, a webhook
146
+ * receiver — and the model never sees the call. Anything the *model* should be
147
+ * able to reach belongs in `tools/`, not here.
148
+ *
149
+ * The path comes from the file's location, `/api` prefix included:
150
+ * `api/cal/slots.ts` is served at `/api/cal/slots`.
151
+ */
152
+ export type EndpointDefinition = {
153
+ /**
154
+ * Restrict the endpoint to these methods, answering anything else with 405.
155
+ * Defaults to accepting every method.
156
+ */
157
+ method?: HttpMethod | HttpMethod[];
158
+ /**
159
+ * Answer cross-origin requests, preflight included. On by default: a widget
160
+ * is served from a different origin than the server it calls, so an endpoint
161
+ * without CORS is one the widget cannot reach.
162
+ */
163
+ cors?: boolean;
164
+ /**
165
+ * Parse a JSON request body into `req.body`. On by default — the framework
166
+ * installs no body parser of its own, so an endpoint without this reads
167
+ * `req.body` as `undefined`.
168
+ */
169
+ json?: boolean;
170
+ /** An Express handler. Throwing is safe: the runtime answers 500 and logs. */
171
+ handler: RequestHandler;
138
172
  };
173
+
174
+ export function defineEndpoint(def: EndpointDefinition): EndpointDefinition {
175
+ return def;
176
+ }