skybridge 0.0.0-dev.e3e0986 → 0.0.0-dev.e4c9359
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 +4 -1
- package/dist/src/server/index.d.ts +2 -2
- package/dist/src/server/inferUtilityTypes.d.ts +30 -15
- package/dist/src/server/server.d.ts +25 -9
- package/dist/src/server/server.js +4 -0
- package/dist/src/server/server.js.map +1 -1
- package/dist/src/test/utils.d.ts +61 -0
- package/dist/src/test/utils.js +121 -0
- package/dist/src/test/utils.js.map +1 -1
- package/dist/src/web/hooks/use-call-tool.d.ts +37 -0
- package/dist/src/web/hooks/use-tool-info.d.ts +1 -1
- package/dist/src/web/typed-hooks.d.ts +69 -18
- package/dist/src/web/typed-hooks.js +53 -7
- package/dist/src/web/typed-hooks.js.map +1 -1
- package/dist/src/web/typed-hooks.test-d.js +39 -21
- package/dist/src/web/typed-hooks.test-d.js.map +1 -1
- package/dist/src/web/typed-hooks.test.js +7 -0
- package/dist/src/web/typed-hooks.test.js.map +1 -1
- package/dist/src/web/types.d.ts +4 -0
- package/dist/src/web/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,6 +101,8 @@ export default defineConfig({
|
|
|
101
101
|
|
|
102
102
|
Skybridge provides fully typed hooks that give you autocomplete for tool names and type inference for inputs/outputs - similar to tRPC. This is opt-in and requires exporting your server type.
|
|
103
103
|
|
|
104
|
+
> **Tip:** For the best TypeScript experience, use typed hooks throughout your application. They provide autocomplete, type safety, and better IDE support.
|
|
105
|
+
|
|
104
106
|
_Server setup (server/src/index.ts)_
|
|
105
107
|
|
|
106
108
|
```ts
|
|
@@ -116,6 +118,7 @@ const server = new McpServer({ name: "my-app", version: "1.0" }, {})
|
|
|
116
118
|
},
|
|
117
119
|
outputSchema: {
|
|
118
120
|
results: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
121
|
+
totalCount: z.number(),
|
|
119
122
|
},
|
|
120
123
|
}, async ({ destination }) => {
|
|
121
124
|
// Your tool logic here...
|
|
@@ -133,7 +136,7 @@ export type AppType = typeof server;
|
|
|
133
136
|
|
|
134
137
|
_One-time setup (web/src/skybridge.ts)_
|
|
135
138
|
|
|
136
|
-
Create typed hooks once and export them for use across your app:
|
|
139
|
+
Create typed hooks once and export them for use across your app. This file acts as a bridge between your server types and your widgets:
|
|
137
140
|
|
|
138
141
|
```ts
|
|
139
142
|
import type { AppType } from "../server"; // type-only import
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { McpServer } from "./server.js";
|
|
2
2
|
export { widgetsDevServer } from "./widgetsDevServer.js";
|
|
3
|
-
export type {
|
|
4
|
-
export type {
|
|
3
|
+
export type { ToolDef } from "./server.js";
|
|
4
|
+
export type { InferTools, AnyToolRegistry, ToolNames, ToolInput, ToolOutput, } from "./inferUtilityTypes.js";
|
|
@@ -1,33 +1,48 @@
|
|
|
1
|
-
import type { McpServer,
|
|
2
|
-
/** Any widget registry shape */
|
|
3
|
-
export type AnyWidgetRegistry = Record<string, WidgetDef>;
|
|
1
|
+
import type { McpServer, ToolDef } from "./server.js";
|
|
4
2
|
/**
|
|
5
|
-
*
|
|
3
|
+
* Any tool registry shape (includes both widgets and regular tools).
|
|
4
|
+
* Used as a constraint for type parameters that accept tool registries.
|
|
5
|
+
*/
|
|
6
|
+
export type AnyToolRegistry = Record<string, ToolDef>;
|
|
7
|
+
/**
|
|
8
|
+
* Extract the tool registry type from an McpServer instance.
|
|
9
|
+
* This includes both widgets (registered via widget()) and regular tools (registered via registerTool()).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* type MyTools = InferTools<MyServer>;
|
|
14
|
+
* // { "search": ToolDef<...>, "calculate": ToolDef<...> }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export type InferTools<T> = T extends McpServer<infer W> ? W : never;
|
|
18
|
+
type ExtractTool<T, K extends ToolNames<T>> = InferTools<T>[K];
|
|
19
|
+
/**
|
|
20
|
+
* Get a union of all tool names from an McpServer instance.
|
|
21
|
+
* This includes both widgets and regular tools.
|
|
6
22
|
*
|
|
7
23
|
* @example
|
|
8
24
|
* ```ts
|
|
9
|
-
* type
|
|
10
|
-
* //
|
|
25
|
+
* type Names = ToolNames<MyServer>;
|
|
26
|
+
* // "search" | "calculate" | "details"
|
|
11
27
|
* ```
|
|
12
28
|
*/
|
|
13
|
-
export type
|
|
14
|
-
/** Get a union of all widget names from an McpServer instance */
|
|
15
|
-
export type WidgetNames<T> = keyof InferWidgets<T> & string;
|
|
29
|
+
export type ToolNames<T> = keyof InferTools<T> & string;
|
|
16
30
|
/**
|
|
17
|
-
* Get the input type for a specific widget.
|
|
31
|
+
* Get the input type for a specific tool (widget or regular tool).
|
|
18
32
|
*
|
|
19
33
|
* @example
|
|
20
34
|
* ```ts
|
|
21
|
-
* type SearchInput =
|
|
35
|
+
* type SearchInput = ToolInput<MyServer, "search">;
|
|
22
36
|
* ```
|
|
23
37
|
*/
|
|
24
|
-
export type
|
|
38
|
+
export type ToolInput<T, K extends ToolNames<T>> = ExtractTool<T, K>["input"];
|
|
25
39
|
/**
|
|
26
|
-
* Get the output type for a specific widget.
|
|
40
|
+
* Get the output type for a specific tool (widget or regular tool).
|
|
27
41
|
*
|
|
28
42
|
* @example
|
|
29
43
|
* ```ts
|
|
30
|
-
* type SearchOutput =
|
|
44
|
+
* type SearchOutput = ToolOutput<MyServer, "search">;
|
|
31
45
|
* ```
|
|
32
46
|
*/
|
|
33
|
-
export type
|
|
47
|
+
export type ToolOutput<T, K extends ToolNames<T>> = ExtractTool<T, K>["output"];
|
|
48
|
+
export {};
|
|
@@ -1,19 +1,35 @@
|
|
|
1
|
-
import { McpServer as McpServerBase, type
|
|
2
|
-
import type { Resource } from "@modelcontextprotocol/sdk/types.js";
|
|
1
|
+
import { McpServer as McpServerBase, type RegisteredTool } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { Resource, ToolAnnotations, CallToolResult, ServerRequest, ServerNotification } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
3
4
|
import type { ZodRawShape, ZodObject, infer as Infer } from "zod";
|
|
4
|
-
export type
|
|
5
|
+
export type ToolDef<TInput = unknown, TOutput = unknown> = {
|
|
5
6
|
input: TInput;
|
|
6
7
|
output: TOutput;
|
|
7
8
|
};
|
|
8
9
|
type McpServerOriginalResourceConfig = Omit<Resource, "uri" | "name" | "mimeType">;
|
|
9
10
|
type McpServerOriginalToolConfig = Omit<Parameters<McpServerBase["registerTool"]>[1], "inputSchema" | "outputSchema">;
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
type ExtractStructuredContent<T> = T extends {
|
|
12
|
+
structuredContent: infer SC;
|
|
13
|
+
} ? SC : {};
|
|
14
|
+
type AddTool<TTools, TName extends string, TInput extends ZodRawShape, TOutput> = McpServer<TTools & {
|
|
15
|
+
[K in TName]: ToolDef<Infer<ZodObject<TInput>>, TOutput>;
|
|
16
|
+
}>;
|
|
17
|
+
type ToolConfig<TInput extends ZodRawShape> = {
|
|
18
|
+
title?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
inputSchema?: TInput;
|
|
21
|
+
outputSchema?: ZodRawShape;
|
|
22
|
+
annotations?: ToolAnnotations;
|
|
23
|
+
_meta?: Record<string, unknown>;
|
|
24
|
+
};
|
|
25
|
+
type ToolHandler<TInput extends ZodRawShape, TReturn extends CallToolResult = CallToolResult> = (args: Infer<ZodObject<TInput>>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => TReturn | Promise<TReturn>;
|
|
26
|
+
export declare class McpServer<TTools extends Record<string, ToolDef> = {}> extends McpServerBase {
|
|
27
|
+
widget<TName extends string, TInput extends ZodRawShape, TReturn extends CallToolResult>(name: TName, resourceConfig: McpServerOriginalResourceConfig, toolConfig: McpServerOriginalToolConfig & {
|
|
12
28
|
inputSchema?: TInput;
|
|
13
|
-
outputSchema?:
|
|
14
|
-
}, toolCallback:
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
outputSchema?: ZodRawShape;
|
|
30
|
+
}, toolCallback: ToolHandler<TInput, TReturn>): AddTool<TTools, TName, TInput, ExtractStructuredContent<TReturn>>;
|
|
31
|
+
registerTool<TName extends string, InputArgs extends ZodRawShape, TReturn extends CallToolResult>(name: TName, config: ToolConfig<InputArgs>, cb: ToolHandler<InputArgs, TReturn>): AddTool<TTools, TName, InputArgs, ExtractStructuredContent<TReturn>>;
|
|
32
|
+
registerTool<InputArgs extends ZodRawShape>(name: string, config: ToolConfig<InputArgs>, cb: ToolHandler<InputArgs>): RegisteredTool;
|
|
17
33
|
private lookupDistFile;
|
|
18
34
|
}
|
|
19
35
|
export {};
|
|
@@ -50,6 +50,10 @@ export class McpServer extends McpServerBase {
|
|
|
50
50
|
}, toolCallback);
|
|
51
51
|
return this;
|
|
52
52
|
}
|
|
53
|
+
registerTool(name, config, cb) {
|
|
54
|
+
super.registerTool(name, config, cb);
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
53
57
|
lookupDistFile(key) {
|
|
54
58
|
const manifest = JSON.parse(readFileSync(path.join(process.cwd(), "dist", "assets", ".vite", "manifest.json"), "utf-8"));
|
|
55
59
|
return manifest[key]?.file;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/server/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,IAAI,aAAa,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/server/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,IAAI,aAAa,GAG3B,MAAM,yCAAyC,CAAC;AAUjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAqF7B,MAAM,OAAO,SAEX,SAAQ,aAAa;IACrB,MAAM,CAKJ,IAAW,EACX,cAA+C,EAC/C,UAGC,EACD,YAA0C;QAE1C,MAAM,GAAG,GAAG,gBAAgB,IAAI,OAAO,CAAC;QACxC,MAAM,gBAAgB,GAAiB;YACrC,GAAG,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE,CAAC;SAChC,CAAC;QACF,IAAI,UAAU,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACzC,gBAAgB,CAAC,0BAA0B,CAAC,GAAG,UAAU,CAAC,WAAW,CAAC;QACxE,CAAC;QAED,IAAI,CAAC,QAAQ,CACX,IAAI,EACJ,GAAG,EACH;YACE,GAAG,cAAc;YACjB,KAAK,EAAE,gBAAgB;SACxB,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;YACpB,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;gBACnC,CAAC,CAAC,WACE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,kBAAkB,CAAC;oBACjD,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,IAC/B,EAAE;gBACJ,CAAC,CAAC,uBAAuB,CAAC;YAE9B,MAAM,IAAI,GACR,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;gBACnC,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC;oBAC9B,SAAS;oBACT,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC,eAAe,IAAI,MAAM,CAAC;oBAC1D,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC;iBAC5C,CAAC;gBACJ,CAAC,CAAC,cAAc,CAAC,iBAAiB,CAAC;oBAC/B,SAAS;oBACT,UAAU,EAAE,IAAI;iBACjB,CAAC,CAAC;YAET,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG;wBACH,QAAQ,EAAE,qBAAqB;wBAC/B,IAAI,EAAE,IAAI;qBACX;iBACF;aACF,CAAC;QACJ,CAAC,CACF,CAAC;QAEF,MAAM,QAAQ,GAAa;YACzB,GAAG,UAAU,CAAC,KAAK;YACnB,uBAAuB,EAAE,GAAG;YAC5B,gBAAgB,EAAE,GAAG;SACtB,CAAC;QAEF,IAAI,CAAC,YAAY,CACf,IAAI,EACJ;YACE,GAAG,UAAU;YACb,KAAK,EAAE,QAAQ;SAChB,EACD,YAAY,CACb,CAAC;QAEF,OAAO,IAAI,CAAC;IACd,CAAC;IAkBQ,YAAY,CACnB,IAAY,EACZ,MAA6B,EAC7B,EAA2B;QAE3B,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,cAAc,CAAC,GAAW;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,YAAY,CACV,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,CAAC,EACpE,OAAO,CACR,CACF,CAAC;QAEF,OAAO,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IAC7B,CAAC;CACF"}
|
package/dist/src/test/utils.d.ts
CHANGED
|
@@ -8,6 +8,67 @@ export declare function createMockMcpServer(): {
|
|
|
8
8
|
mockResource: MockInstance<McpServer["resource"]>;
|
|
9
9
|
mockRegisterTool: MockInstance<McpServer["registerTool"]>;
|
|
10
10
|
};
|
|
11
|
+
export declare function createTestServer(): McpServer<{
|
|
12
|
+
"search-voyage": import("../server/server.js").ToolDef<{
|
|
13
|
+
destination: string;
|
|
14
|
+
departureDate?: string | undefined;
|
|
15
|
+
maxPrice?: number | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
results: {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
price: number;
|
|
21
|
+
}[];
|
|
22
|
+
totalCount: number;
|
|
23
|
+
}>;
|
|
24
|
+
} & {
|
|
25
|
+
"get-trip-details": import("../server/server.js").ToolDef<{
|
|
26
|
+
tripId: string;
|
|
27
|
+
}, {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
images: string[];
|
|
31
|
+
}>;
|
|
32
|
+
} & {
|
|
33
|
+
"no-input-widget": import("../server/server.js").ToolDef<{}, {}>;
|
|
34
|
+
} & {
|
|
35
|
+
"inferred-output-widget": import("../server/server.js").ToolDef<{
|
|
36
|
+
query: string;
|
|
37
|
+
}, {
|
|
38
|
+
inferredResults: {
|
|
39
|
+
id: string;
|
|
40
|
+
score: number;
|
|
41
|
+
}[];
|
|
42
|
+
inferredCount: number;
|
|
43
|
+
}>;
|
|
44
|
+
} & {
|
|
45
|
+
"calculate-price": import("../server/server.js").ToolDef<{
|
|
46
|
+
tripId: string;
|
|
47
|
+
passengers: number;
|
|
48
|
+
}, {
|
|
49
|
+
totalPrice: number;
|
|
50
|
+
currency: string;
|
|
51
|
+
}>;
|
|
52
|
+
} & {
|
|
53
|
+
"inferred-tool": import("../server/server.js").ToolDef<{
|
|
54
|
+
itemId: string;
|
|
55
|
+
}, {
|
|
56
|
+
itemDetails: {
|
|
57
|
+
name: string;
|
|
58
|
+
available: boolean;
|
|
59
|
+
};
|
|
60
|
+
fetchedAt: string;
|
|
61
|
+
}>;
|
|
62
|
+
}>;
|
|
63
|
+
export declare function createMinimalTestServer(): McpServer<{
|
|
64
|
+
"search-voyage": import("../server/server.js").ToolDef<{
|
|
65
|
+
destination: string;
|
|
66
|
+
}, {
|
|
67
|
+
results: {
|
|
68
|
+
id: string;
|
|
69
|
+
}[];
|
|
70
|
+
}>;
|
|
71
|
+
}>;
|
|
11
72
|
/**
|
|
12
73
|
* Mock extra parameter for resource callback
|
|
13
74
|
*/
|
package/dist/src/test/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { vi } from "vitest";
|
|
2
2
|
import { McpServer } from "../server/server.js";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
/**
|
|
4
5
|
* Creates a real McpServer instance for testing
|
|
5
6
|
*/
|
|
@@ -18,6 +19,126 @@ export function createMockMcpServer() {
|
|
|
18
19
|
mockRegisterTool,
|
|
19
20
|
};
|
|
20
21
|
}
|
|
22
|
+
export function createTestServer() {
|
|
23
|
+
return new McpServer({ name: "test-app", version: "1.0.0" }, {})
|
|
24
|
+
.widget("search-voyage", {}, {
|
|
25
|
+
description: "Search for voyages",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
destination: z.string(),
|
|
28
|
+
departureDate: z.string().optional(),
|
|
29
|
+
maxPrice: z.number().optional(),
|
|
30
|
+
},
|
|
31
|
+
outputSchema: {
|
|
32
|
+
results: z.array(z.object({
|
|
33
|
+
id: z.string(),
|
|
34
|
+
name: z.string(),
|
|
35
|
+
price: z.number(),
|
|
36
|
+
})),
|
|
37
|
+
totalCount: z.number(),
|
|
38
|
+
},
|
|
39
|
+
}, async ({ destination }) => {
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `Found trips to ${destination}` }],
|
|
42
|
+
structuredContent: {
|
|
43
|
+
results: [{ id: "1", name: "Trip", price: 1000 }],
|
|
44
|
+
totalCount: 1,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
})
|
|
48
|
+
.widget("get-trip-details", {}, {
|
|
49
|
+
description: "Get trip details",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
tripId: z.string(),
|
|
52
|
+
},
|
|
53
|
+
outputSchema: {
|
|
54
|
+
name: z.string(),
|
|
55
|
+
description: z.string(),
|
|
56
|
+
images: z.array(z.string()),
|
|
57
|
+
},
|
|
58
|
+
}, async ({ tripId }) => {
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: `Details for ${tripId}` }],
|
|
61
|
+
structuredContent: {
|
|
62
|
+
name: "Trip",
|
|
63
|
+
description: "A great trip",
|
|
64
|
+
images: ["image1.jpg"],
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
})
|
|
68
|
+
.widget("no-input-widget", {}, {
|
|
69
|
+
description: "Widget with no input",
|
|
70
|
+
inputSchema: {},
|
|
71
|
+
outputSchema: {},
|
|
72
|
+
}, async () => {
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: "No input needed" }],
|
|
75
|
+
structuredContent: {},
|
|
76
|
+
};
|
|
77
|
+
})
|
|
78
|
+
.widget("inferred-output-widget", {}, {
|
|
79
|
+
description: "Widget with output inferred from callback",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
query: z.string(),
|
|
82
|
+
},
|
|
83
|
+
}, async ({ query }) => {
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: `Query: ${query}` }],
|
|
86
|
+
structuredContent: {
|
|
87
|
+
inferredResults: [{ id: "inferred-1", score: 0.95 }],
|
|
88
|
+
inferredCount: 1,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
})
|
|
92
|
+
.registerTool("calculate-price", {
|
|
93
|
+
description: "Calculate trip price",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
tripId: z.string(),
|
|
96
|
+
passengers: z.number(),
|
|
97
|
+
},
|
|
98
|
+
outputSchema: {
|
|
99
|
+
totalPrice: z.number(),
|
|
100
|
+
currency: z.string(),
|
|
101
|
+
},
|
|
102
|
+
}, async ({ tripId, passengers }) => {
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: "text", text: `Price for ${tripId}` }],
|
|
105
|
+
structuredContent: {
|
|
106
|
+
totalPrice: 1000 * passengers,
|
|
107
|
+
currency: "USD",
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
})
|
|
111
|
+
.registerTool("inferred-tool", {
|
|
112
|
+
description: "Tool with output inferred from callback",
|
|
113
|
+
inputSchema: {
|
|
114
|
+
itemId: z.string(),
|
|
115
|
+
},
|
|
116
|
+
}, async ({ itemId }) => {
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: "text", text: `Item: ${itemId}` }],
|
|
119
|
+
structuredContent: {
|
|
120
|
+
itemDetails: { name: "Inferred Item", available: true },
|
|
121
|
+
fetchedAt: "2024-01-01",
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export function createMinimalTestServer() {
|
|
127
|
+
return new McpServer({ name: "test-app", version: "1.0.0" }, {}).widget("search-voyage", {}, {
|
|
128
|
+
description: "Search for voyages",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
destination: z.string(),
|
|
131
|
+
},
|
|
132
|
+
outputSchema: {
|
|
133
|
+
results: z.array(z.object({ id: z.string() })),
|
|
134
|
+
},
|
|
135
|
+
}, async ({ destination }) => {
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: `Found trips to ${destination}` }],
|
|
138
|
+
structuredContent: { results: [{ id: "1" }] },
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
}
|
|
21
142
|
/**
|
|
22
143
|
* Mock extra parameter for resource callback
|
|
23
144
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/test/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAqB,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/test/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,41 @@
|
|
|
1
1
|
import type { CallToolArgs, CallToolResponse } from "../types.js";
|
|
2
|
+
type CallToolIdleState = {
|
|
3
|
+
status: "idle";
|
|
4
|
+
isIdle: true;
|
|
5
|
+
isPending: false;
|
|
6
|
+
isSuccess: false;
|
|
7
|
+
isError: false;
|
|
8
|
+
data: undefined;
|
|
9
|
+
error: undefined;
|
|
10
|
+
};
|
|
11
|
+
type CallToolPendingState = {
|
|
12
|
+
status: "pending";
|
|
13
|
+
isIdle: false;
|
|
14
|
+
isPending: true;
|
|
15
|
+
isSuccess: false;
|
|
16
|
+
isError: false;
|
|
17
|
+
data: undefined;
|
|
18
|
+
error: undefined;
|
|
19
|
+
};
|
|
20
|
+
type CallToolSuccessState<TData extends CallToolResponse = CallToolResponse> = {
|
|
21
|
+
status: "success";
|
|
22
|
+
isIdle: false;
|
|
23
|
+
isPending: false;
|
|
24
|
+
isSuccess: true;
|
|
25
|
+
isError: false;
|
|
26
|
+
data: TData;
|
|
27
|
+
error: undefined;
|
|
28
|
+
};
|
|
29
|
+
type CallToolErrorState = {
|
|
30
|
+
status: "error";
|
|
31
|
+
isIdle: false;
|
|
32
|
+
isPending: false;
|
|
33
|
+
isSuccess: false;
|
|
34
|
+
isError: true;
|
|
35
|
+
data: undefined;
|
|
36
|
+
error: unknown;
|
|
37
|
+
};
|
|
38
|
+
export type CallToolState<TData extends CallToolResponse = CallToolResponse> = CallToolIdleState | CallToolPendingState | CallToolSuccessState<TData> | CallToolErrorState;
|
|
2
39
|
type SideEffects<ToolArgs, ToolResponse> = {
|
|
3
40
|
onSuccess?: (data: ToolResponse, toolArgs: ToolArgs) => void;
|
|
4
41
|
onError?: (error: unknown, toolArgs: ToolArgs) => void;
|
|
@@ -15,7 +15,7 @@ export type ToolSuccessState<ToolInput extends UnknownObject, ToolOutput extends
|
|
|
15
15
|
output: ToolOutput;
|
|
16
16
|
responseMetadata: ToolResponseMetadata;
|
|
17
17
|
};
|
|
18
|
-
type ToolState<ToolInput extends UnknownObject, ToolOutput extends UnknownObject, ToolResponseMetadata extends UnknownObject> = ToolPendingState<ToolInput> | ToolSuccessState<ToolInput, ToolOutput, ToolResponseMetadata>;
|
|
18
|
+
export type ToolState<ToolInput extends UnknownObject, ToolOutput extends UnknownObject, ToolResponseMetadata extends UnknownObject> = ToolPendingState<ToolInput> | ToolSuccessState<ToolInput, ToolOutput, ToolResponseMetadata>;
|
|
19
19
|
type ToolSignature = {
|
|
20
20
|
input: UnknownObject;
|
|
21
21
|
output: UnknownObject;
|
|
@@ -1,16 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import type { McpServer,
|
|
4
|
-
import type {
|
|
5
|
-
type TypedCallToolReturn<TInput, TOutput> =
|
|
6
|
-
structuredContent: TOutput
|
|
7
|
-
}
|
|
8
|
-
|
|
1
|
+
import { type CallToolState } from "./hooks/use-call-tool.js";
|
|
2
|
+
import { type ToolState } from "./hooks/use-tool-info.js";
|
|
3
|
+
import type { McpServer, InferTools, AnyToolRegistry, ToolInput, ToolOutput } from "../server/index.js";
|
|
4
|
+
import type { CallToolResponse, Prettify, Objectify } from "./types.js";
|
|
5
|
+
type TypedCallToolReturn<TInput, TOutput> = Prettify<CallToolState<CallToolResponse & {
|
|
6
|
+
structuredContent: TOutput;
|
|
7
|
+
}> & {
|
|
8
|
+
callTool: (args: TInput) => void;
|
|
9
|
+
callToolAsync: (args: TInput) => Promise<CallToolResponse & {
|
|
10
|
+
structuredContent: TOutput;
|
|
11
|
+
}>;
|
|
12
|
+
}>;
|
|
13
|
+
type TypedToolInfoReturn<TInput, TOutput> = ToolState<Objectify<TInput>, Objectify<TOutput>, Objectify<{}>>;
|
|
9
14
|
/**
|
|
10
15
|
* Creates typed versions of skybridge hooks with full type inference
|
|
11
16
|
* for tool names, inputs, and outputs.
|
|
12
17
|
*
|
|
13
|
-
*
|
|
18
|
+
* This is the recommended way to use skybridge hooks in your widgets.
|
|
19
|
+
* Set this up once in a dedicated file and export the typed hooks for use across your app.
|
|
20
|
+
*
|
|
21
|
+
* @typeParam T - The type of your McpServer instance. Use `typeof server`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // server/src/index.ts
|
|
26
|
+
* const server = new McpServer({ name: "my-app", version: "1.0" }, {})
|
|
27
|
+
* .widget("search-voyage", {}, {
|
|
28
|
+
* inputSchema: { destination: z.string() },
|
|
29
|
+
* outputSchema: { results: z.array(z.string()) },
|
|
30
|
+
* }, async ({ destination }) => {
|
|
31
|
+
* return { content: [{ type: "text", text: `Found trips to ${destination}` }] };
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* export type AppType = typeof server;
|
|
35
|
+
* ```
|
|
14
36
|
*
|
|
15
37
|
* @example
|
|
16
38
|
* ```typescript
|
|
@@ -21,41 +43,70 @@ type TypedToolInfoReturn<TInput extends UnknownObject, TOutput extends UnknownOb
|
|
|
21
43
|
* export const { useCallTool, useToolInfo } = createTypedHooks<AppType>();
|
|
22
44
|
* ```
|
|
23
45
|
*
|
|
46
|
+
* @example
|
|
24
47
|
* ```typescript
|
|
25
48
|
* // web/src/widgets/search.tsx (usage)
|
|
26
49
|
* import { useCallTool, useToolInfo } from "../skybridge";
|
|
27
50
|
*
|
|
28
51
|
* export function SearchWidget() {
|
|
29
|
-
* const { callTool, data } = useCallTool("search");
|
|
52
|
+
* const { callTool, data } = useCallTool("search-voyage");
|
|
30
53
|
* // ^ autocomplete for tool names
|
|
31
|
-
* callTool({
|
|
54
|
+
* callTool({ destination: "Spain" });
|
|
32
55
|
* // ^ autocomplete for input fields
|
|
33
56
|
*
|
|
34
|
-
* const toolInfo = useToolInfo<"search">();
|
|
35
|
-
* // ^ autocomplete for
|
|
57
|
+
* const toolInfo = useToolInfo<"search-voyage">();
|
|
58
|
+
* // ^ autocomplete for widget names
|
|
36
59
|
* // toolInfo.input is typed based on widget input schema
|
|
37
60
|
* // toolInfo.output is typed based on widget output schema
|
|
38
61
|
* }
|
|
39
62
|
* ```
|
|
40
63
|
*/
|
|
41
|
-
export declare function createTypedHooks<T extends McpServer<
|
|
64
|
+
export declare function createTypedHooks<T extends McpServer<AnyToolRegistry>>(): {
|
|
42
65
|
/**
|
|
43
|
-
* Typed version of useCallTool that provides autocomplete for tool names
|
|
66
|
+
* Typed version of `useCallTool` that provides autocomplete for tool names
|
|
44
67
|
* and type inference for inputs and outputs.
|
|
68
|
+
*
|
|
69
|
+
* @param name - The name of the widget to call. Autocompletes based on your server's widget registry.
|
|
70
|
+
* @returns A hook with typed `callTool` function and `data` property.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const { callTool, data, isPending } = useCallTool("search-voyage");
|
|
75
|
+
* // TypeScript knows callTool expects { destination: string }
|
|
76
|
+
* callTool({ destination: "Spain" });
|
|
77
|
+
*
|
|
78
|
+
* // data.structuredContent is typed based on your outputSchema
|
|
79
|
+
* if (data) {
|
|
80
|
+
* console.log(data.structuredContent.results);
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
45
83
|
*/
|
|
46
|
-
useCallTool: <K extends keyof
|
|
84
|
+
useCallTool: <K extends keyof InferTools<T> & string>(name: K) => TypedCallToolReturn<InferTools<T>[K]["input"], InferTools<T>[K]["output"]>;
|
|
47
85
|
/**
|
|
48
|
-
* Typed version of useToolInfo that provides autocomplete for widget names
|
|
86
|
+
* Typed version of `useToolInfo` that provides autocomplete for widget names
|
|
49
87
|
* and type inference for inputs, outputs, and responseMetadata.
|
|
50
88
|
*
|
|
89
|
+
* @typeParam K - The name of the widget. Autocompletes based on your server's widget registry.
|
|
90
|
+
* @returns A discriminated union with `status: "pending" | "success"` that narrows correctly.
|
|
91
|
+
*
|
|
51
92
|
* @example
|
|
52
93
|
* ```typescript
|
|
53
94
|
* const toolInfo = useToolInfo<"search-voyage">();
|
|
54
95
|
* // toolInfo.input is typed as { destination: string; ... }
|
|
55
96
|
* // toolInfo.output is typed as { results: Array<...>; ... } | undefined
|
|
56
97
|
* // toolInfo.status narrows correctly: "pending" | "success"
|
|
98
|
+
*
|
|
99
|
+
* if (toolInfo.isPending) {
|
|
100
|
+
* // TypeScript knows output is undefined here
|
|
101
|
+
* console.log(toolInfo.input.destination);
|
|
102
|
+
* }
|
|
103
|
+
*
|
|
104
|
+
* if (toolInfo.isSuccess) {
|
|
105
|
+
* // TypeScript knows output is defined here
|
|
106
|
+
* console.log(toolInfo.output.results);
|
|
107
|
+
* }
|
|
57
108
|
* ```
|
|
58
109
|
*/
|
|
59
|
-
useToolInfo: <K extends keyof
|
|
110
|
+
useToolInfo: <K extends keyof InferTools<T> & string>() => TypedToolInfoReturn<ToolInput<T, K>, ToolOutput<T, K>>;
|
|
60
111
|
};
|
|
61
112
|
export {};
|
|
@@ -4,7 +4,24 @@ import { useToolInfo } from "./hooks/use-tool-info.js";
|
|
|
4
4
|
* Creates typed versions of skybridge hooks with full type inference
|
|
5
5
|
* for tool names, inputs, and outputs.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* This is the recommended way to use skybridge hooks in your widgets.
|
|
8
|
+
* Set this up once in a dedicated file and export the typed hooks for use across your app.
|
|
9
|
+
*
|
|
10
|
+
* @typeParam T - The type of your McpServer instance. Use `typeof server`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // server/src/index.ts
|
|
15
|
+
* const server = new McpServer({ name: "my-app", version: "1.0" }, {})
|
|
16
|
+
* .widget("search-voyage", {}, {
|
|
17
|
+
* inputSchema: { destination: z.string() },
|
|
18
|
+
* outputSchema: { results: z.array(z.string()) },
|
|
19
|
+
* }, async ({ destination }) => {
|
|
20
|
+
* return { content: [{ type: "text", text: `Found trips to ${destination}` }] };
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* export type AppType = typeof server;
|
|
24
|
+
* ```
|
|
8
25
|
*
|
|
9
26
|
* @example
|
|
10
27
|
* ```typescript
|
|
@@ -15,18 +32,19 @@ import { useToolInfo } from "./hooks/use-tool-info.js";
|
|
|
15
32
|
* export const { useCallTool, useToolInfo } = createTypedHooks<AppType>();
|
|
16
33
|
* ```
|
|
17
34
|
*
|
|
35
|
+
* @example
|
|
18
36
|
* ```typescript
|
|
19
37
|
* // web/src/widgets/search.tsx (usage)
|
|
20
38
|
* import { useCallTool, useToolInfo } from "../skybridge";
|
|
21
39
|
*
|
|
22
40
|
* export function SearchWidget() {
|
|
23
|
-
* const { callTool, data } = useCallTool("search");
|
|
41
|
+
* const { callTool, data } = useCallTool("search-voyage");
|
|
24
42
|
* // ^ autocomplete for tool names
|
|
25
|
-
* callTool({
|
|
43
|
+
* callTool({ destination: "Spain" });
|
|
26
44
|
* // ^ autocomplete for input fields
|
|
27
45
|
*
|
|
28
|
-
* const toolInfo = useToolInfo<"search">();
|
|
29
|
-
* // ^ autocomplete for
|
|
46
|
+
* const toolInfo = useToolInfo<"search-voyage">();
|
|
47
|
+
* // ^ autocomplete for widget names
|
|
30
48
|
* // toolInfo.input is typed based on widget input schema
|
|
31
49
|
* // toolInfo.output is typed based on widget output schema
|
|
32
50
|
* }
|
|
@@ -35,22 +53,50 @@ import { useToolInfo } from "./hooks/use-tool-info.js";
|
|
|
35
53
|
export function createTypedHooks() {
|
|
36
54
|
return {
|
|
37
55
|
/**
|
|
38
|
-
* Typed version of useCallTool that provides autocomplete for tool names
|
|
56
|
+
* Typed version of `useCallTool` that provides autocomplete for tool names
|
|
39
57
|
* and type inference for inputs and outputs.
|
|
58
|
+
*
|
|
59
|
+
* @param name - The name of the widget to call. Autocompletes based on your server's widget registry.
|
|
60
|
+
* @returns A hook with typed `callTool` function and `data` property.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const { callTool, data, isPending } = useCallTool("search-voyage");
|
|
65
|
+
* // TypeScript knows callTool expects { destination: string }
|
|
66
|
+
* callTool({ destination: "Spain" });
|
|
67
|
+
*
|
|
68
|
+
* // data.structuredContent is typed based on your outputSchema
|
|
69
|
+
* if (data) {
|
|
70
|
+
* console.log(data.structuredContent.results);
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
40
73
|
*/
|
|
41
74
|
useCallTool: (name) => {
|
|
42
75
|
return useCallTool(name);
|
|
43
76
|
},
|
|
44
77
|
/**
|
|
45
|
-
* Typed version of useToolInfo that provides autocomplete for widget names
|
|
78
|
+
* Typed version of `useToolInfo` that provides autocomplete for widget names
|
|
46
79
|
* and type inference for inputs, outputs, and responseMetadata.
|
|
47
80
|
*
|
|
81
|
+
* @typeParam K - The name of the widget. Autocompletes based on your server's widget registry.
|
|
82
|
+
* @returns A discriminated union with `status: "pending" | "success"` that narrows correctly.
|
|
83
|
+
*
|
|
48
84
|
* @example
|
|
49
85
|
* ```typescript
|
|
50
86
|
* const toolInfo = useToolInfo<"search-voyage">();
|
|
51
87
|
* // toolInfo.input is typed as { destination: string; ... }
|
|
52
88
|
* // toolInfo.output is typed as { results: Array<...>; ... } | undefined
|
|
53
89
|
* // toolInfo.status narrows correctly: "pending" | "success"
|
|
90
|
+
*
|
|
91
|
+
* if (toolInfo.isPending) {
|
|
92
|
+
* // TypeScript knows output is undefined here
|
|
93
|
+
* console.log(toolInfo.input.destination);
|
|
94
|
+
* }
|
|
95
|
+
*
|
|
96
|
+
* if (toolInfo.isSuccess) {
|
|
97
|
+
* // TypeScript knows output is defined here
|
|
98
|
+
* console.log(toolInfo.output.results);
|
|
99
|
+
* }
|
|
54
100
|
* ```
|
|
55
101
|
*/
|
|
56
102
|
useToolInfo: () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-hooks.js","sourceRoot":"","sources":["../../../src/web/typed-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"typed-hooks.js","sourceRoot":"","sources":["../../../src/web/typed-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAsB,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAuBvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,UAAU,gBAAgB;IAI9B,OAAO;QACL;;;;;;;;;;;;;;;;;;WAkBG;QACH,WAAW,EAAE,CACX,IAAO,EACqD,EAAE;YAC9D,OAAO,WAAW,CAAC,IAAI,CAGtB,CAAC;QACJ,CAAC;QAED;;;;;;;;;;;;;;;;;;;;;;;;WAwBG;QACH,WAAW,EAAE,GAGX,EAAE;YACF,OAAO,WAAW,EAGjB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,27 +1,42 @@
|
|
|
1
1
|
import { expectTypeOf, test } from "vitest";
|
|
2
2
|
import { createTypedHooks } from "./typed-hooks.js";
|
|
3
|
-
|
|
3
|
+
import { createTestServer } from "../test/utils.js";
|
|
4
|
+
const server = createTestServer();
|
|
5
|
+
test("InferTools extracts the tool registry type (widgets + registerTool)", () => {
|
|
4
6
|
expectTypeOf().toHaveProperty("search-voyage");
|
|
5
7
|
expectTypeOf().toHaveProperty("get-trip-details");
|
|
6
8
|
expectTypeOf().toHaveProperty("no-input-widget");
|
|
9
|
+
expectTypeOf().toHaveProperty("calculate-price");
|
|
10
|
+
expectTypeOf().toHaveProperty("inferred-output-widget");
|
|
11
|
+
expectTypeOf().toHaveProperty("inferred-tool");
|
|
7
12
|
});
|
|
8
|
-
test("
|
|
13
|
+
test("ToolNames returns a union of tool name literals (widgets + registerTool)", () => {
|
|
9
14
|
expectTypeOf().toEqualTypeOf();
|
|
10
15
|
});
|
|
11
|
-
test("
|
|
16
|
+
test("ToolInput extracts the correct input type from Zod schema", () => {
|
|
17
|
+
expectTypeOf().toEqualTypeOf();
|
|
12
18
|
expectTypeOf().toEqualTypeOf();
|
|
13
19
|
expectTypeOf().toEqualTypeOf();
|
|
14
20
|
});
|
|
15
|
-
test("
|
|
21
|
+
test("ToolOutput extracts the correct output type from callback's structuredContent", () => {
|
|
22
|
+
expectTypeOf().toEqualTypeOf();
|
|
23
|
+
expectTypeOf().toEqualTypeOf();
|
|
16
24
|
expectTypeOf().toEqualTypeOf();
|
|
17
25
|
expectTypeOf().toEqualTypeOf();
|
|
18
26
|
});
|
|
19
|
-
test("
|
|
27
|
+
test("ToolOutput extracts the correct output type from callback (inferred)", () => {
|
|
28
|
+
expectTypeOf().toEqualTypeOf();
|
|
29
|
+
expectTypeOf().toEqualTypeOf();
|
|
30
|
+
});
|
|
31
|
+
test("createTypedHooks provides autocomplete for tool names (widgets + registerTool)", () => {
|
|
20
32
|
const { useCallTool } = createTypedHooks();
|
|
21
33
|
useCallTool("search-voyage");
|
|
22
34
|
useCallTool("get-trip-details");
|
|
23
35
|
useCallTool("no-input-widget");
|
|
24
|
-
|
|
36
|
+
useCallTool("calculate-price");
|
|
37
|
+
useCallTool("inferred-output-widget");
|
|
38
|
+
useCallTool("inferred-tool");
|
|
39
|
+
// @ts-expect-error - "invalid-name" is not a valid tool name
|
|
25
40
|
useCallTool("invalid-name");
|
|
26
41
|
});
|
|
27
42
|
test("useCallTool returns correctly typed callTool function", () => {
|
|
@@ -30,6 +45,8 @@ test("useCallTool returns correctly typed callTool function", () => {
|
|
|
30
45
|
callTool({ destination: "Spain" });
|
|
31
46
|
callTool({ destination: "France", departureDate: "2024-06-01" });
|
|
32
47
|
callTool({ destination: "Italy", maxPrice: 1000 });
|
|
48
|
+
const { callTool: calculateTool } = useCallTool("calculate-price");
|
|
49
|
+
calculateTool({ tripId: "123", passengers: 2 });
|
|
33
50
|
});
|
|
34
51
|
test("useCallTool returns correctly typed data", () => {
|
|
35
52
|
const { useCallTool } = createTypedHooks();
|
|
@@ -40,33 +57,34 @@ test("useCallTool returns correctly typed data", () => {
|
|
|
40
57
|
expectTypeOf(data.structuredContent.totalCount).toBeNumber();
|
|
41
58
|
}
|
|
42
59
|
});
|
|
43
|
-
test("
|
|
44
|
-
|
|
60
|
+
test("useCallTool returns correctly typed data for callback-inferred outputs", () => {
|
|
61
|
+
const { useCallTool } = createTypedHooks();
|
|
62
|
+
const { data: widgetData } = useCallTool("inferred-output-widget");
|
|
63
|
+
if (widgetData) {
|
|
64
|
+
expectTypeOf(widgetData.structuredContent).toExtend();
|
|
65
|
+
}
|
|
66
|
+
const { data: toolData } = useCallTool("inferred-tool");
|
|
67
|
+
if (toolData) {
|
|
68
|
+
expectTypeOf(toolData.structuredContent).toExtend();
|
|
69
|
+
}
|
|
45
70
|
});
|
|
46
|
-
test("createTypedHooks provides autocomplete for
|
|
71
|
+
test("createTypedHooks provides autocomplete for tool names in useToolInfo (widgets + registerTool)", () => {
|
|
47
72
|
const { useToolInfo } = createTypedHooks();
|
|
48
73
|
useToolInfo();
|
|
49
74
|
useToolInfo();
|
|
50
75
|
useToolInfo();
|
|
51
|
-
|
|
76
|
+
useToolInfo();
|
|
77
|
+
useToolInfo();
|
|
78
|
+
useToolInfo();
|
|
79
|
+
// @ts-expect-error - "invalid-name" is not a valid tool name
|
|
52
80
|
useToolInfo();
|
|
53
81
|
});
|
|
54
|
-
test("useToolInfo infers input
|
|
82
|
+
test("useToolInfo infers input and output types", () => {
|
|
55
83
|
const { useToolInfo } = createTypedHooks();
|
|
56
84
|
const toolInfo = useToolInfo();
|
|
57
85
|
expectTypeOf(toolInfo.input).toExtend();
|
|
58
|
-
const detailsInfo = useToolInfo();
|
|
59
|
-
expectTypeOf(detailsInfo.input).toExtend();
|
|
60
|
-
});
|
|
61
|
-
test("useToolInfo infers output types from WidgetOutput utility", () => {
|
|
62
|
-
const { useToolInfo } = createTypedHooks();
|
|
63
|
-
const toolInfo = useToolInfo();
|
|
64
86
|
if (toolInfo.status === "success") {
|
|
65
87
|
expectTypeOf(toolInfo.output).toExtend();
|
|
66
88
|
}
|
|
67
|
-
const detailsInfo = useToolInfo();
|
|
68
|
-
if (detailsInfo.status === "success") {
|
|
69
|
-
expectTypeOf(detailsInfo.output).toExtend();
|
|
70
|
-
}
|
|
71
89
|
});
|
|
72
90
|
//# sourceMappingURL=typed-hooks.test-d.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-hooks.test-d.js","sourceRoot":"","sources":["../../../src/web/typed-hooks.test-d.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"typed-hooks.test-d.js","sourceRoot":"","sources":["../../../src/web/typed-hooks.test-d.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { createTypedHooks } from "./typed-hooks.js";
|
|
3
|
+
import { createMinimalTestServer } from "../test/utils.js";
|
|
4
|
+
const server = createMinimalTestServer();
|
|
3
5
|
describe("createTypedHooks", () => {
|
|
4
6
|
it("should return an object with useCallTool hook", () => {
|
|
5
7
|
const hooks = createTypedHooks();
|
|
6
8
|
expect(hooks).toHaveProperty("useCallTool");
|
|
7
9
|
expect(typeof hooks.useCallTool).toBe("function");
|
|
8
10
|
});
|
|
11
|
+
it("should return an object with useToolInfo hook", () => {
|
|
12
|
+
const hooks = createTypedHooks();
|
|
13
|
+
expect(hooks).toHaveProperty("useToolInfo");
|
|
14
|
+
expect(typeof hooks.useToolInfo).toBe("function");
|
|
15
|
+
});
|
|
9
16
|
});
|
|
10
17
|
//# sourceMappingURL=typed-hooks.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-hooks.test.js","sourceRoot":"","sources":["../../../src/web/typed-hooks.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"typed-hooks.test.js","sourceRoot":"","sources":["../../../src/web/typed-hooks.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,MAAM,MAAM,GAAG,uBAAuB,EAAE,CAAC;AAGzC,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,KAAK,GAAG,gBAAgB,EAAc,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,KAAK,GAAG,gBAAgB,EAAc,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/src/web/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/web/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/web/types.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,wBAAwB,GAAG,sBAAsB,CAAC;AAC/D,MAAM,OAAO,iBAAkB,SAAQ,WAErC;IACkB,IAAI,GAAG,wBAAwB,CAAC;CACnD;AA8FD,sDAAsD;AACtD,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAC3D,MAAM,OAAO,eAAgB,SAAQ,WAEnC;IACkB,IAAI,GAAG,sBAAsB,CAAC;CACjD"}
|