@nifrajs/mcp 1.13.0 → 2.1.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 ADDED
@@ -0,0 +1,33 @@
1
+ # @nifrajs/mcp
2
+
3
+ Build transport-agnostic MCP servers and interactive MCP Apps for Nifra applications.
4
+
5
+ ```sh
6
+ bun add @nifrajs/mcp
7
+ ```
8
+
9
+ ```ts
10
+ import { createMcpServer, defineMcpTool } from "@nifrajs/mcp"
11
+
12
+ const tools = [
13
+ defineMcpTool({
14
+ name: "health",
15
+ description: "Report service health",
16
+ handler: () => ({ text: "ok" }),
17
+ }),
18
+ ]
19
+
20
+ const mcp = createMcpServer({ name: "orders", version: "1.0.0", tools })
21
+ ```
22
+
23
+ Mount `mcp.fetch` at `POST /mcp`. The package also exposes the JSON-RPC protocol and Streamable HTTP
24
+ layers directly, plus `defineMcpWidget` and the React adapter for tool results that render UI in MCP
25
+ hosts.
26
+
27
+ ## For AI agents
28
+
29
+ Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
30
+ one cheap read instead of the whole corpus. For the wider framework: the repo's
31
+ [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
32
+ [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
33
+ done-gate, or `nifra mcp` to give the agent live project tools.
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export { bridgeScript, type McpAppBridge } from "./bridge.js";
12
12
  export { type McpHttpOptions, respondMcpHttp } from "./http.js";
13
13
  export * from "./protocol.js";
14
14
  export { type CreateMcpServerOptions, createMcpServer, type McpServer } from "./server.js";
15
+ export type { InferOutput, StandardIssue, StandardResult, StandardSchemaV1, } from "./standard.js";
15
16
  export { type DefineMcpToolOptions, defineMcpTool, type McpToolHandlerResult, type McpUiIntent, } from "./tool.js";
16
17
  export { type DefineMcpWidgetOptions, defineMcpWidget, type McpWidget, uiResourceMeta, widgetDocument, } from "./widget.js";
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,KAAK,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC/D,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AAC1F,OAAO,EACL,KAAK,oBAAoB,EACzB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,KAAK,sBAAsB,EAC3B,eAAe,EACf,KAAK,SAAS,EACd,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,KAAK,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC/D,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AAC1F,YAAY,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,KAAK,oBAAoB,EACzB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,KAAK,sBAAsB,EAC3B,eAAe,EACf,KAAK,SAAS,EACd,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAA"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAqB,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAuB,cAAc,EAAE,MAAM,WAAW,CAAA;AAC/D,cAAc,eAAe,CAAA;AAC7B,OAAO,EAA+B,eAAe,EAAkB,MAAM,aAAa,CAAA;AAC1F,OAAO,EAEL,aAAa,GAGd,MAAM,WAAW,CAAA;AAClB,OAAO,EAEL,eAAe,EAEf,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAqB,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAuB,cAAc,EAAE,MAAM,WAAW,CAAA;AAC/D,cAAc,eAAe,CAAA;AAC7B,OAAO,EAA+B,eAAe,EAAkB,MAAM,aAAa,CAAA;AAO1F,OAAO,EAEL,aAAa,GAGd,MAAM,WAAW,CAAA;AAClB,OAAO,EAEL,eAAe,EAEf,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAA"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The Standard Schema v1 interface (https://standardschema.dev), vendored as types + a tiny
3
+ * runtime so any compliant validator - nifra's `t`, zod, valibot, arktype, … - can type and
4
+ * validate tool arguments without coupling this package to a validator. The spec is MIT-licensed
5
+ * and explicitly designed to be copied; this package stays dependency-free.
6
+ */
7
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
8
+ readonly "~standard": {
9
+ readonly version: 1;
10
+ readonly vendor: string;
11
+ readonly validate: (value: unknown) => StandardResult<Output> | Promise<StandardResult<Output>>;
12
+ readonly types?: {
13
+ readonly input: Input;
14
+ readonly output: Output;
15
+ } | undefined;
16
+ };
17
+ }
18
+ export type StandardResult<Output> = {
19
+ readonly value: Output;
20
+ readonly issues?: undefined;
21
+ } | {
22
+ readonly issues: ReadonlyArray<StandardIssue>;
23
+ };
24
+ export interface StandardIssue {
25
+ readonly message: string;
26
+ readonly path?: ReadonlyArray<PropertyKey | {
27
+ readonly key: PropertyKey;
28
+ }> | undefined;
29
+ }
30
+ /** The validated (post-transform) type of a Standard Schema. */
31
+ export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"];
32
+ /** Render issues as one agent-readable line per problem: `location: message`. */
33
+ export declare function formatIssues(issues: ReadonlyArray<StandardIssue>): string;
34
+ /**
35
+ * The wire JSON Schema of a Standard Schema, when it carries one. nifra's `t` schemas ARE
36
+ * JSON Schema (exposed as `.jsonSchema`), so tools authored with `t` advertise a full
37
+ * `inputSchema` for free. Schemas from other vendors return `undefined` - pass an explicit
38
+ * `inputSchema` alongside `input` for those.
39
+ */
40
+ export declare function jsonSchemaOf(schema: StandardSchemaV1): Record<string, unknown> | undefined;
41
+ //# sourceMappingURL=standard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard.d.ts","sourceRoot":"","sources":["../src/standard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;IAC/D,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;QACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,cAAc,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA;QAC/F,QAAQ,CAAC,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAA;KAChF,CAAA;CACF;AAED,MAAM,MAAM,cAAc,CAAC,MAAM,IAC7B;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;CAAE,CAAA;AAErD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,GAAG;QAAE,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;KAAE,CAAC,GAAG,SAAS,CAAA;CACvF;AAED,gEAAgE;AAChE,MAAM,MAAM,WAAW,CAAC,MAAM,SAAS,gBAAgB,IAAI,WAAW,CACpE,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAC7B,CAAC,QAAQ,CAAC,CAAA;AAEX,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,MAAM,CASzE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAK1F"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The Standard Schema v1 interface (https://standardschema.dev), vendored as types + a tiny
3
+ * runtime so any compliant validator - nifra's `t`, zod, valibot, arktype, … - can type and
4
+ * validate tool arguments without coupling this package to a validator. The spec is MIT-licensed
5
+ * and explicitly designed to be copied; this package stays dependency-free.
6
+ */
7
+ /** Render issues as one agent-readable line per problem: `location: message`. */
8
+ export function formatIssues(issues) {
9
+ return issues
10
+ .map((issue) => {
11
+ const path = (issue.path ?? [])
12
+ .map((seg) => String(typeof seg === "object" && seg !== null ? seg.key : seg))
13
+ .join(".");
14
+ return path === "" ? issue.message : `${path}: ${issue.message}`;
15
+ })
16
+ .join("; ");
17
+ }
18
+ /**
19
+ * The wire JSON Schema of a Standard Schema, when it carries one. nifra's `t` schemas ARE
20
+ * JSON Schema (exposed as `.jsonSchema`), so tools authored with `t` advertise a full
21
+ * `inputSchema` for free. Schemas from other vendors return `undefined` - pass an explicit
22
+ * `inputSchema` alongside `input` for those.
23
+ */
24
+ export function jsonSchemaOf(schema) {
25
+ const candidate = schema.jsonSchema;
26
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate)
27
+ ? candidate
28
+ : undefined;
29
+ }
30
+ //# sourceMappingURL=standard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard.js","sourceRoot":"","sources":["../src/standard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyBH,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,MAAoC;IAC/D,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;aAC5B,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;aAC7E,IAAI,CAAC,GAAG,CAAC,CAAA;QACZ,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAA;IAClE,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAwB;IACnD,MAAM,SAAS,GAAI,MAAmC,CAAC,UAAU,CAAA;IACjE,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QACrF,CAAC,CAAE,SAAqC;QACxC,CAAC,CAAC,SAAS,CAAA;AACf,CAAC"}
package/dist/tool.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * `tools/call` result, so the host knows to render the widget — see {@link ../protocol.ts}.
8
8
  */
9
9
  import type { McpContentBlock, McpTool, McpToolContext } from "./protocol.js";
10
+ import type { InferOutput, StandardSchemaV1 } from "./standard.js";
10
11
  import type { McpWidget } from "./widget.js";
11
12
  /** The ergonomic result an MCP-tool handler may return (coerced to the protocol's {@link McpToolResult}). */
12
13
  export type McpToolHandlerResult = string | {
@@ -26,10 +27,23 @@ export type McpToolHandlerResult = string | {
26
27
  * union — pick a known tag or a custom one.
27
28
  */
28
29
  export type McpUiIntent = "table" | "list" | "cards" | "form" | "metric" | "detail" | "chart" | (string & {});
29
- export interface DefineMcpToolOptions {
30
+ /** The default when no `input` schema is given: arguments arrive untyped and unvalidated. */
31
+ type UntypedArgs = StandardSchemaV1<unknown, Record<string, unknown>>;
32
+ export interface DefineMcpToolOptions<S extends StandardSchemaV1 = UntypedArgs> {
30
33
  readonly name: string;
31
34
  readonly description: string;
32
- /** JSON Schema for the arguments. Defaults to an empty object schema. */
35
+ /**
36
+ * A Standard Schema for the arguments (nifra's `t`, zod, valibot, arktype, …). It validates every
37
+ * call before the handler runs - invalid arguments return an in-band `isError` result naming each
38
+ * issue, so the calling agent can correct and retry - and it types the handler's `args`. Schemas
39
+ * that carry a JSON Schema (nifra's `t` does) also become the advertised `inputSchema` for free.
40
+ */
41
+ readonly input?: S;
42
+ /**
43
+ * JSON Schema for the wire descriptor (`tools/list`). Defaults to `input`'s own JSON Schema when
44
+ * it exposes one, else an empty object schema. Pass explicitly when `input` comes from a vendor
45
+ * whose schemas carry no JSON Schema, so the agent still sees real parameter docs.
46
+ */
33
47
  readonly inputSchema?: Record<string, unknown>;
34
48
  /** Link a widget so the tool's result renders as interactive UI in MCP Apps hosts (iframe). */
35
49
  readonly widget?: McpWidget;
@@ -37,7 +51,8 @@ export interface DefineMcpToolOptions {
37
51
  * in `_meta.ui.intent`. Independent of `widget` — a tool can offer both (widget for MCP Apps hosts,
38
52
  * intent for generative builders). */
39
53
  readonly intent?: McpUiIntent;
40
- readonly handler: (args: Record<string, unknown>, context: McpToolContext) => McpToolHandlerResult | Promise<McpToolHandlerResult>;
54
+ readonly handler: (args: InferOutput<S>, context: McpToolContext) => McpToolHandlerResult | Promise<McpToolHandlerResult>;
41
55
  }
42
- export declare function defineMcpTool(opts: DefineMcpToolOptions): McpTool;
56
+ export declare function defineMcpTool<S extends StandardSchemaV1 = UntypedArgs>(opts: DefineMcpToolOptions<S>): McpTool;
57
+ export {};
43
58
  //# sourceMappingURL=tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,EAAiB,MAAM,eAAe,CAAA;AAC5F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,6GAA6G;AAC7G,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN;IACE,yFAAyF;IACzF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,kDAAkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAC7C,gFAAgF;IAChF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAC3B,CAAA;AAEL;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,MAAM,GACN,OAAO,GACP,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjB,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9C,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;IAC3B;;0CAEsC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,KACpB,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;CAC1D;AAmCD,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CASjE"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,EAAiB,MAAM,eAAe,CAAA;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAElE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,6GAA6G;AAC7G,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN;IACE,yFAAyF;IACzF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,kDAAkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IAC7C,gFAAgF;IAChF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAC3B,CAAA;AAEL;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,MAAM,GACN,OAAO,GACP,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjB,6FAA6F;AAC7F,KAAK,WAAW,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAErE,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,gBAAgB,GAAG,WAAW;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAClB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9C,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;IAC3B;;0CAEsC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAChB,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE,cAAc,KACpB,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;CAC1D;AAgCD,wBAAgB,aAAa,CAAC,CAAC,SAAS,gBAAgB,GAAG,WAAW,EACpE,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC5B,OAAO,CA2BT"}
package/dist/tool.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * `widget` is passed, the tool advertises it via `_meta.ui.resourceUri` on both `tools/list` and each
7
7
  * `tools/call` result, so the host knows to render the widget — see {@link ../protocol.ts}.
8
8
  */
9
+ import { formatIssues, jsonSchemaOf } from "./standard.js";
9
10
  /** Build a tool's descriptor `_meta` from its widget link and/or render intent (both under `ui`). */
10
11
  function toolMeta(widget, intent) {
11
12
  if (widget === undefined && intent === undefined)
@@ -15,11 +16,7 @@ function toolMeta(widget, intent) {
15
16
  ui.resourceUri = widget.uri;
16
17
  if (intent !== undefined)
17
18
  ui.intent = intent;
18
- const meta = { ui };
19
- // Deprecated-flat alias for hosts that still read it.
20
- if (widget !== undefined)
21
- meta["ui/resourceUri"] = widget.uri;
22
- return meta;
19
+ return { ui };
23
20
  }
24
21
  const EMPTY_OBJECT_SCHEMA = {
25
22
  type: "object",
@@ -42,12 +39,29 @@ function coerce(result) {
42
39
  }
43
40
  export function defineMcpTool(opts) {
44
41
  const meta = toolMeta(opts.widget, opts.intent);
42
+ const input = opts.input;
45
43
  return {
46
44
  name: opts.name,
47
45
  description: opts.description,
48
- inputSchema: opts.inputSchema ?? EMPTY_OBJECT_SCHEMA,
46
+ inputSchema: opts.inputSchema ??
47
+ (input !== undefined ? jsonSchemaOf(input) : undefined) ??
48
+ EMPTY_OBJECT_SCHEMA,
49
49
  ...(meta !== undefined ? { _meta: meta } : {}),
50
- handler: async (args, context) => coerce(await opts.handler(args, context)),
50
+ handler: async (args, context) => {
51
+ let validated = args;
52
+ if (input !== undefined) {
53
+ const result = await input["~standard"].validate(args);
54
+ if (result.issues !== undefined) {
55
+ // In-band failure (not a protocol error): the agent reads the issues and retries corrected.
56
+ return {
57
+ content: [{ type: "text", text: `Invalid arguments: ${formatIssues(result.issues)}` }],
58
+ isError: true,
59
+ };
60
+ }
61
+ validated = result.value;
62
+ }
63
+ return coerce(await opts.handler(validated, context));
64
+ },
51
65
  };
52
66
  }
53
67
  //# sourceMappingURL=tool.js.map
package/dist/tool.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tool.js","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAoDH,qGAAqG;AACrG,SAAS,QAAQ,CAAC,MAAkB,EAAE,MAAe;IACnD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAClE,MAAM,EAAE,GAA4B,EAAE,CAAA;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,EAAE,CAAC,WAAW,GAAG,MAAM,CAAC,GAAG,CAAA;IACrD,IAAI,MAAM,KAAK,SAAS;QAAE,EAAE,CAAC,MAAM,GAAG,MAAM,CAAA;IAC5C,MAAM,IAAI,GAA4B,EAAE,EAAE,EAAE,CAAA;IAC5C,sDAAsD;IACtD,IAAI,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,GAAG,CAAA;IAC7D,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,mBAAmB,GAA4B;IACnD,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,EAAE;IACd,oBAAoB,EAAE,KAAK;CAC5B,CAAA;AAED,SAAS,MAAM,CAAC,MAA4B;IAC1C,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAA;IAC7C,MAAM,OAAO,GACX,MAAM,CAAC,OAAO;QACd,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACjF,OAAO;QACL,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,MAAM,CAAC,iBAAiB,KAAK,SAAS;YACxC,CAAC,CAAC,EAAE,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE;YACjD,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAA0B;IACtD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAC/C,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,mBAAmB;QACpD,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;KAC5E,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"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"}
package/dist/widget.d.ts CHANGED
@@ -27,8 +27,7 @@ export interface DefineMcpWidgetOptions {
27
27
  * `mcpApp.callTool(...)`. The bridge global is defined before this runs. */
28
28
  readonly html: string;
29
29
  }
30
- /** The MCP Apps `_meta.ui.resourceUri` link, emitted nested (current spec) AND flat (the deprecated
31
- * `ui/resourceUri`, still read by some hosts) so a tool renders across the transition. */
30
+ /** The MCP Apps `_meta.ui.resourceUri` link. */
32
31
  export declare function uiResourceMeta(uri: string): Record<string, unknown>;
33
32
  /** A widget: the resource to register on the server, its `ui://` URI, and the `_meta` link for its tool. */
34
33
  export interface McpWidget {
@@ -1 +1 @@
1
- {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,KAAK,WAAW,EAAW,MAAM,eAAe,CAAA;AAEzD,MAAM,WAAW,sBAAsB;IACrC,yFAAyF;IACzF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,sGAAsG;IACtG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;gFAC4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;0FAC0F;AAC1F,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnE;AAED,4GAA4G;AAC5G,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACvC;AAMD;4BAC4B;AAC5B,wBAAgB,cAAc,CAAC,IAAI,EAAE,sBAAsB,GAAG,MAAM,CAgBnE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,SAAS,CAavE"}
1
+ {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,KAAK,WAAW,EAAW,MAAM,eAAe,CAAA;AAEzD,MAAM,WAAW,sBAAsB;IACrC,yFAAyF;IACzF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,sGAAsG;IACtG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;gFAC4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnE;AAED,4GAA4G;AAC5G,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACvC;AAMD;4BAC4B;AAC5B,wBAAgB,cAAc,CAAC,IAAI,EAAE,sBAAsB,GAAG,MAAM,CAgBnE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,SAAS,CAavE"}
package/dist/widget.js CHANGED
@@ -14,10 +14,9 @@
14
14
  */
15
15
  import { bridgeScript } from "./bridge.js";
16
16
  import { UI_MIME } from "./protocol.js";
17
- /** The MCP Apps `_meta.ui.resourceUri` link, emitted nested (current spec) AND flat (the deprecated
18
- * `ui/resourceUri`, still read by some hosts) so a tool renders across the transition. */
17
+ /** The MCP Apps `_meta.ui.resourceUri` link. */
19
18
  export function uiResourceMeta(uri) {
20
- return { ui: { resourceUri: uri }, "ui/resourceUri": uri };
19
+ return { ui: { resourceUri: uri } };
21
20
  }
22
21
  function escapeHtml(text) {
23
22
  return text.replace(/[&<>]/g, (c) => (c === "&" ? "&amp;" : c === "<" ? "&lt;" : "&gt;"));
@@ -1 +1 @@
1
- {"version":3,"file":"widget.js","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAoB,OAAO,EAAE,MAAM,eAAe,CAAA;AAiBzD;0FAC0F;AAC1F,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAA;AAC5D,CAAC;AASD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED;4BAC4B;AAC5B,MAAM,UAAU,cAAc,CAAC,IAA4B;IACzD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,CAAA;IACjD,OAAO;;;;;SAKA,KAAK;UACJ,YAAY,EAAE;EACtB,IAAI,CAAC,IAAI,IAAI,EAAE;;;EAGf,IAAI,CAAC,IAAI;;;CAGV,CAAA;AACD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAA4B;IAC1D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,wDAAwD,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IACvF,CAAC;IACD,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,QAAQ,GAAgB;QAC5B,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;KAC/D,CAAA;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;AACpE,CAAC"}
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAoB,OAAO,EAAE,MAAM,eAAe,CAAA;AAiBzD,gDAAgD;AAChD,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,CAAA;AACrC,CAAC;AASD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED;4BAC4B;AAC5B,MAAM,UAAU,cAAc,CAAC,IAA4B;IACzD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,CAAA;IACjD,OAAO;;;;;SAKA,KAAK;UACJ,YAAY,EAAE;EACtB,IAAI,CAAC,IAAI,IAAI,EAAE;;;EAGf,IAAI,CAAC,IAAI;;;CAGV,CAAA;AACD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAA4B;IAC1D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,wDAAwD,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IACvF,CAAC;IACD,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,QAAQ,GAAgB;QAC5B,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;KAC/D,CAAA;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;AACpE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/mcp",
3
- "version": "1.13.0",
3
+ "version": "2.1.0",
4
4
  "description": "Build MCP servers — and MCP Apps (interactive `ui://` widgets, SEP-1865) — for a nifra app. The transport-agnostic JSON-RPC core (also used by `@nifrajs/cli`'s dev MCP), a Streamable-HTTP handler you mount at `POST /mcp`, and `defineMcpWidget`/`defineMcpTool` for tool results that render real UI in MCP hosts.",
5
5
  "keywords": [
6
6
  "bun",
@@ -53,6 +53,9 @@
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  },
56
+ "devDependencies": {
57
+ "react": "19.2.7"
58
+ },
56
59
  "repository": {
57
60
  "type": "git",
58
61
  "url": "git+https://github.com/nifrajs/nifra.git",
package/src/index.ts CHANGED
@@ -13,6 +13,12 @@ export { bridgeScript, type McpAppBridge } from "./bridge.ts"
13
13
  export { type McpHttpOptions, respondMcpHttp } from "./http.ts"
14
14
  export * from "./protocol.ts"
15
15
  export { type CreateMcpServerOptions, createMcpServer, type McpServer } from "./server.ts"
16
+ export type {
17
+ InferOutput,
18
+ StandardIssue,
19
+ StandardResult,
20
+ StandardSchemaV1,
21
+ } from "./standard.ts"
16
22
  export {
17
23
  type DefineMcpToolOptions,
18
24
  defineMcpTool,
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The Standard Schema v1 interface (https://standardschema.dev), vendored as types + a tiny
3
+ * runtime so any compliant validator - nifra's `t`, zod, valibot, arktype, … - can type and
4
+ * validate tool arguments without coupling this package to a validator. The spec is MIT-licensed
5
+ * and explicitly designed to be copied; this package stays dependency-free.
6
+ */
7
+
8
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
9
+ readonly "~standard": {
10
+ readonly version: 1
11
+ readonly vendor: string
12
+ readonly validate: (value: unknown) => StandardResult<Output> | Promise<StandardResult<Output>>
13
+ readonly types?: { readonly input: Input; readonly output: Output } | undefined
14
+ }
15
+ }
16
+
17
+ export type StandardResult<Output> =
18
+ | { readonly value: Output; readonly issues?: undefined }
19
+ | { readonly issues: ReadonlyArray<StandardIssue> }
20
+
21
+ export interface StandardIssue {
22
+ readonly message: string
23
+ readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }> | undefined
24
+ }
25
+
26
+ /** The validated (post-transform) type of a Standard Schema. */
27
+ export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<
28
+ Schema["~standard"]["types"]
29
+ >["output"]
30
+
31
+ /** Render issues as one agent-readable line per problem: `location: message`. */
32
+ export function formatIssues(issues: ReadonlyArray<StandardIssue>): string {
33
+ return issues
34
+ .map((issue) => {
35
+ const path = (issue.path ?? [])
36
+ .map((seg) => String(typeof seg === "object" && seg !== null ? seg.key : seg))
37
+ .join(".")
38
+ return path === "" ? issue.message : `${path}: ${issue.message}`
39
+ })
40
+ .join("; ")
41
+ }
42
+
43
+ /**
44
+ * The wire JSON Schema of a Standard Schema, when it carries one. nifra's `t` schemas ARE
45
+ * JSON Schema (exposed as `.jsonSchema`), so tools authored with `t` advertise a full
46
+ * `inputSchema` for free. Schemas from other vendors return `undefined` - pass an explicit
47
+ * `inputSchema` alongside `input` for those.
48
+ */
49
+ export function jsonSchemaOf(schema: StandardSchemaV1): Record<string, unknown> | undefined {
50
+ const candidate = (schema as { jsonSchema?: unknown }).jsonSchema
51
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate)
52
+ ? (candidate as Record<string, unknown>)
53
+ : undefined
54
+ }
package/src/tool.ts CHANGED
@@ -8,6 +8,8 @@
8
8
  */
9
9
 
10
10
  import type { McpContentBlock, McpTool, McpToolContext, McpToolResult } from "./protocol.ts"
11
+ import type { InferOutput, StandardSchemaV1 } from "./standard.ts"
12
+ import { formatIssues, jsonSchemaOf } from "./standard.ts"
11
13
  import type { McpWidget } from "./widget.ts"
12
14
 
13
15
  /** The ergonomic result an MCP-tool handler may return (coerced to the protocol's {@link McpToolResult}). */
@@ -40,10 +42,24 @@ export type McpUiIntent =
40
42
  | "chart"
41
43
  | (string & {})
42
44
 
43
- export interface DefineMcpToolOptions {
45
+ /** The default when no `input` schema is given: arguments arrive untyped and unvalidated. */
46
+ type UntypedArgs = StandardSchemaV1<unknown, Record<string, unknown>>
47
+
48
+ export interface DefineMcpToolOptions<S extends StandardSchemaV1 = UntypedArgs> {
44
49
  readonly name: string
45
50
  readonly description: string
46
- /** JSON Schema for the arguments. Defaults to an empty object schema. */
51
+ /**
52
+ * A Standard Schema for the arguments (nifra's `t`, zod, valibot, arktype, …). It validates every
53
+ * call before the handler runs - invalid arguments return an in-band `isError` result naming each
54
+ * issue, so the calling agent can correct and retry - and it types the handler's `args`. Schemas
55
+ * that carry a JSON Schema (nifra's `t` does) also become the advertised `inputSchema` for free.
56
+ */
57
+ readonly input?: S
58
+ /**
59
+ * JSON Schema for the wire descriptor (`tools/list`). Defaults to `input`'s own JSON Schema when
60
+ * it exposes one, else an empty object schema. Pass explicitly when `input` comes from a vendor
61
+ * whose schemas carry no JSON Schema, so the agent still sees real parameter docs.
62
+ */
47
63
  readonly inputSchema?: Record<string, unknown>
48
64
  /** Link a widget so the tool's result renders as interactive UI in MCP Apps hosts (iframe). */
49
65
  readonly widget?: McpWidget
@@ -52,7 +68,7 @@ export interface DefineMcpToolOptions {
52
68
  * intent for generative builders). */
53
69
  readonly intent?: McpUiIntent
54
70
  readonly handler: (
55
- args: Record<string, unknown>,
71
+ args: InferOutput<S>,
56
72
  context: McpToolContext,
57
73
  ) => McpToolHandlerResult | Promise<McpToolHandlerResult>
58
74
  }
@@ -63,10 +79,7 @@ function toolMeta(widget?: McpWidget, intent?: string): Record<string, unknown>
63
79
  const ui: Record<string, unknown> = {}
64
80
  if (widget !== undefined) ui.resourceUri = widget.uri
65
81
  if (intent !== undefined) ui.intent = intent
66
- const meta: Record<string, unknown> = { ui }
67
- // Deprecated-flat alias for hosts that still read it.
68
- if (widget !== undefined) meta["ui/resourceUri"] = widget.uri
69
- return meta
82
+ return { ui }
70
83
  }
71
84
 
72
85
  const EMPTY_OBJECT_SCHEMA: Record<string, unknown> = {
@@ -90,13 +103,33 @@ function coerce(result: McpToolHandlerResult): string | McpToolResult {
90
103
  }
91
104
  }
92
105
 
93
- export function defineMcpTool(opts: DefineMcpToolOptions): McpTool {
106
+ export function defineMcpTool<S extends StandardSchemaV1 = UntypedArgs>(
107
+ opts: DefineMcpToolOptions<S>,
108
+ ): McpTool {
94
109
  const meta = toolMeta(opts.widget, opts.intent)
110
+ const input = opts.input
95
111
  return {
96
112
  name: opts.name,
97
113
  description: opts.description,
98
- inputSchema: opts.inputSchema ?? EMPTY_OBJECT_SCHEMA,
114
+ inputSchema:
115
+ opts.inputSchema ??
116
+ (input !== undefined ? jsonSchemaOf(input) : undefined) ??
117
+ EMPTY_OBJECT_SCHEMA,
99
118
  ...(meta !== undefined ? { _meta: meta } : {}),
100
- handler: async (args, context) => coerce(await opts.handler(args, context)),
119
+ handler: async (args, context) => {
120
+ let validated = args as InferOutput<S>
121
+ if (input !== undefined) {
122
+ const result = await input["~standard"].validate(args)
123
+ if (result.issues !== undefined) {
124
+ // In-band failure (not a protocol error): the agent reads the issues and retries corrected.
125
+ return {
126
+ content: [{ type: "text", text: `Invalid arguments: ${formatIssues(result.issues)}` }],
127
+ isError: true,
128
+ }
129
+ }
130
+ validated = result.value as InferOutput<S>
131
+ }
132
+ return coerce(await opts.handler(validated, context))
133
+ },
101
134
  }
102
135
  }
package/src/widget.ts CHANGED
@@ -31,10 +31,9 @@ export interface DefineMcpWidgetOptions {
31
31
  readonly html: string
32
32
  }
33
33
 
34
- /** The MCP Apps `_meta.ui.resourceUri` link, emitted nested (current spec) AND flat (the deprecated
35
- * `ui/resourceUri`, still read by some hosts) so a tool renders across the transition. */
34
+ /** The MCP Apps `_meta.ui.resourceUri` link. */
36
35
  export function uiResourceMeta(uri: string): Record<string, unknown> {
37
- return { ui: { resourceUri: uri }, "ui/resourceUri": uri }
36
+ return { ui: { resourceUri: uri } }
38
37
  }
39
38
 
40
39
  /** A widget: the resource to register on the server, its `ui://` URI, and the `_meta` link for its tool. */