@nifrajs/mcp 1.13.0 → 2.0.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 +33 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/standard.d.ts +41 -0
- package/dist/standard.d.ts.map +1 -0
- package/dist/standard.js +30 -0
- package/dist/standard.js.map +1 -0
- package/dist/tool.d.ts +19 -4
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +21 -7
- package/dist/tool.js.map +1 -1
- package/dist/widget.d.ts +1 -2
- package/dist/widget.d.ts.map +1 -1
- package/dist/widget.js +2 -3
- package/dist/widget.js.map +1 -1
- package/package.json +4 -1
- package/src/index.ts +6 -0
- package/src/standard.ts +54 -0
- package/src/tool.ts +43 -10
- package/src/widget.ts +2 -3
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/standard.js
ADDED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
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:
|
|
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
|
package/dist/tool.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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 ??
|
|
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) =>
|
|
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;
|
|
1
|
+
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAgE1D,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,OAAO,EAAE,EAAE,EAAE,CAAA;AACf,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,CAC3B,IAA6B;IAE7B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IACxB,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EACT,IAAI,CAAC,WAAW;YAChB,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACvD,mBAAmB;QACrB,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;YAC/B,IAAI,SAAS,GAAG,IAAsB,CAAA;YACtC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;gBACtD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;oBAChC,4FAA4F;oBAC5F,OAAO;wBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;wBACtF,OAAO,EAAE,IAAI;qBACd,CAAA;gBACH,CAAC;gBACD,SAAS,GAAG,MAAM,CAAC,KAAuB,CAAA;YAC5C,CAAC;YACD,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;QACvD,CAAC;KACF,CAAA;AACH,CAAC"}
|
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
|
|
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 {
|
package/dist/widget.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
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 }
|
|
19
|
+
return { ui: { resourceUri: uri } };
|
|
21
20
|
}
|
|
22
21
|
function escapeHtml(text) {
|
|
23
22
|
return text.replace(/[&<>]/g, (c) => (c === "&" ? "&" : c === "<" ? "<" : ">"));
|
package/dist/widget.js.map
CHANGED
|
@@ -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;
|
|
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": "
|
|
3
|
+
"version": "2.0.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,
|
package/src/standard.ts
ADDED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
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) =>
|
|
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
|
|
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 }
|
|
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. */
|