@frontmcp/react 0.0.1
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/LICENSE +201 -0
- package/README.md +263 -0
- package/ai/createToolHandler.d.ts +10 -0
- package/ai/createToolHandler.d.ts.map +1 -0
- package/ai/index.d.ts +7 -0
- package/ai/index.d.ts.map +1 -0
- package/ai/index.js +416 -0
- package/ai/types.d.ts +52 -0
- package/ai/types.d.ts.map +1 -0
- package/ai/useAITools.d.ts +17 -0
- package/ai/useAITools.d.ts.map +1 -0
- package/ai/useTools.d.ts +19 -0
- package/ai/useTools.d.ts.map +1 -0
- package/api/api.types.d.ts +61 -0
- package/api/api.types.d.ts.map +1 -0
- package/api/createFetchClient.d.ts +9 -0
- package/api/createFetchClient.d.ts.map +1 -0
- package/api/index.d.ts +12 -0
- package/api/index.d.ts.map +1 -0
- package/api/index.js +402 -0
- package/api/parseOpenApiSpec.d.ts +9 -0
- package/api/parseOpenApiSpec.d.ts.map +1 -0
- package/api/useApiClient.d.ts +9 -0
- package/api/useApiClient.d.ts.map +1 -0
- package/components/AgentContent.d.ts +30 -0
- package/components/AgentContent.d.ts.map +1 -0
- package/components/AgentSearch.d.ts +35 -0
- package/components/AgentSearch.d.ts.map +1 -0
- package/components/ComponentRegistry.d.ts +36 -0
- package/components/ComponentRegistry.d.ts.map +1 -0
- package/components/DomResources.d.ts +16 -0
- package/components/DomResources.d.ts.map +1 -0
- package/components/DynamicRenderer.d.ts +19 -0
- package/components/DynamicRenderer.d.ts.map +1 -0
- package/components/OutputDisplay.d.ts +11 -0
- package/components/OutputDisplay.d.ts.map +1 -0
- package/components/PromptForm.d.ts +13 -0
- package/components/PromptForm.d.ts.map +1 -0
- package/components/ResourceViewer.d.ts +18 -0
- package/components/ResourceViewer.d.ts.map +1 -0
- package/components/ToolForm.d.ts +16 -0
- package/components/ToolForm.d.ts.map +1 -0
- package/components/index.d.ts +21 -0
- package/components/index.d.ts.map +1 -0
- package/components/mcpComponent.d.ts +48 -0
- package/components/mcpComponent.d.ts.map +1 -0
- package/esm/ai/index.mjs +393 -0
- package/esm/api/index.mjs +379 -0
- package/esm/index.mjs +1814 -0
- package/esm/package.json +111 -0
- package/esm/router/index.mjs +157 -0
- package/esm/state/index.mjs +450 -0
- package/hooks/index.d.ts +21 -0
- package/hooks/index.d.ts.map +1 -0
- package/hooks/useCallTool.d.ts +9 -0
- package/hooks/useCallTool.d.ts.map +1 -0
- package/hooks/useComponentTree.d.ts +21 -0
- package/hooks/useComponentTree.d.ts.map +1 -0
- package/hooks/useDynamicResource.d.ts +20 -0
- package/hooks/useDynamicResource.d.ts.map +1 -0
- package/hooks/useDynamicTool.d.ts +39 -0
- package/hooks/useDynamicTool.d.ts.map +1 -0
- package/hooks/useFrontMcp.d.ts +8 -0
- package/hooks/useFrontMcp.d.ts.map +1 -0
- package/hooks/useGetPrompt.d.ts +13 -0
- package/hooks/useGetPrompt.d.ts.map +1 -0
- package/hooks/useListPrompts.d.ts +10 -0
- package/hooks/useListPrompts.d.ts.map +1 -0
- package/hooks/useListResources.d.ts +14 -0
- package/hooks/useListResources.d.ts.map +1 -0
- package/hooks/useListTools.d.ts +10 -0
- package/hooks/useListTools.d.ts.map +1 -0
- package/hooks/useReadResource.d.ts +23 -0
- package/hooks/useReadResource.d.ts.map +1 -0
- package/hooks/useResolvedServer.d.ts +16 -0
- package/hooks/useResolvedServer.d.ts.map +1 -0
- package/hooks/useServer.d.ts +17 -0
- package/hooks/useServer.d.ts.map +1 -0
- package/hooks/useStoreResource.d.ts +22 -0
- package/hooks/useStoreResource.d.ts.map +1 -0
- package/index.d.ts +33 -0
- package/index.d.ts.map +1 -0
- package/index.js +1821 -0
- package/package.json +111 -0
- package/provider/FrontMcpContext.d.ts +6 -0
- package/provider/FrontMcpContext.d.ts.map +1 -0
- package/provider/FrontMcpProvider.d.ts +34 -0
- package/provider/FrontMcpProvider.d.ts.map +1 -0
- package/provider/index.d.ts +4 -0
- package/provider/index.d.ts.map +1 -0
- package/registry/DynamicRegistry.d.ts +55 -0
- package/registry/DynamicRegistry.d.ts.map +1 -0
- package/registry/ServerRegistry.d.ts +43 -0
- package/registry/ServerRegistry.d.ts.map +1 -0
- package/registry/createWrappedServer.d.ts +14 -0
- package/registry/createWrappedServer.d.ts.map +1 -0
- package/registry/index.d.ts +5 -0
- package/registry/index.d.ts.map +1 -0
- package/router/current-route.resource.d.ts +11 -0
- package/router/current-route.resource.d.ts.map +1 -0
- package/router/go-back.tool.d.ts +18 -0
- package/router/go-back.tool.d.ts.map +1 -0
- package/router/index.d.ts +9 -0
- package/router/index.d.ts.map +1 -0
- package/router/index.js +180 -0
- package/router/navigate.tool.d.ts +35 -0
- package/router/navigate.tool.d.ts.map +1 -0
- package/router/router-bridge.d.ts +20 -0
- package/router/router-bridge.d.ts.map +1 -0
- package/router/router.entries.d.ts +23 -0
- package/router/router.entries.d.ts.map +1 -0
- package/router/useRouterBridge.d.ts +7 -0
- package/router/useRouterBridge.d.ts.map +1 -0
- package/state/adapters/createStore.d.ts +15 -0
- package/state/adapters/createStore.d.ts.map +1 -0
- package/state/adapters/index.d.ts +7 -0
- package/state/adapters/index.d.ts.map +1 -0
- package/state/adapters/reduxAdapter.d.ts +21 -0
- package/state/adapters/reduxAdapter.d.ts.map +1 -0
- package/state/adapters/valtioAdapter.d.ts +19 -0
- package/state/adapters/valtioAdapter.d.ts.map +1 -0
- package/state/index.d.ts +15 -0
- package/state/index.d.ts.map +1 -0
- package/state/index.js +473 -0
- package/state/state.types.d.ts +48 -0
- package/state/state.types.d.ts.map +1 -0
- package/state/useReduxResource.d.ts +8 -0
- package/state/useReduxResource.d.ts.map +1 -0
- package/state/useStoreRegistration.d.ts +14 -0
- package/state/useStoreRegistration.d.ts.map +1 -0
- package/state/useStoreResource.d.ts +10 -0
- package/state/useStoreResource.d.ts.map +1 -0
- package/state/useValtioResource.d.ts +9 -0
- package/state/useValtioResource.d.ts.map +1 -0
- package/types.d.ts +127 -0
- package/types.d.ts.map +1 -0
- package/utils/index.d.ts +2 -0
- package/utils/index.d.ts.map +1 -0
- package/utils/zodToJsonSchema.d.ts +9 -0
- package/utils/zodToJsonSchema.d.ts.map +1 -0
package/esm/package.json
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@frontmcp/react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React hooks, components, and AI SDK integration for FrontMCP",
|
|
5
|
+
"author": "AgentFront <info@agentfront.dev>",
|
|
6
|
+
"homepage": "https://docs.agentfront.dev",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"mcp",
|
|
10
|
+
"react",
|
|
11
|
+
"hooks",
|
|
12
|
+
"ai",
|
|
13
|
+
"tools",
|
|
14
|
+
"openai",
|
|
15
|
+
"claude",
|
|
16
|
+
"vercel-ai",
|
|
17
|
+
"agentfront",
|
|
18
|
+
"frontmcp"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/agentfront/frontmcp.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/agentfront/frontmcp/issues"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "../index.js",
|
|
29
|
+
"module": "./index.mjs",
|
|
30
|
+
"types": "../index.d.ts",
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"exports": {
|
|
33
|
+
"./package.json": "../package.json",
|
|
34
|
+
".": {
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "../index.d.ts",
|
|
37
|
+
"default": "../index.js"
|
|
38
|
+
},
|
|
39
|
+
"import": {
|
|
40
|
+
"types": "../index.d.ts",
|
|
41
|
+
"default": "./index.mjs"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"./ai": {
|
|
45
|
+
"require": {
|
|
46
|
+
"types": "../ai/index.d.ts",
|
|
47
|
+
"default": "../ai/index.js"
|
|
48
|
+
},
|
|
49
|
+
"import": {
|
|
50
|
+
"types": "../ai/index.d.ts",
|
|
51
|
+
"default": "./ai/index.mjs"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"./router": {
|
|
55
|
+
"require": {
|
|
56
|
+
"types": "../router/index.d.ts",
|
|
57
|
+
"default": "../router/index.js"
|
|
58
|
+
},
|
|
59
|
+
"import": {
|
|
60
|
+
"types": "../router/index.d.ts",
|
|
61
|
+
"default": "./router/index.mjs"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"./state": {
|
|
65
|
+
"require": {
|
|
66
|
+
"types": "../state/index.d.ts",
|
|
67
|
+
"default": "../state/index.js"
|
|
68
|
+
},
|
|
69
|
+
"import": {
|
|
70
|
+
"types": "../state/index.d.ts",
|
|
71
|
+
"default": "./state/index.mjs"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"./api": {
|
|
75
|
+
"require": {
|
|
76
|
+
"types": "../api/index.d.ts",
|
|
77
|
+
"default": "../api/index.js"
|
|
78
|
+
},
|
|
79
|
+
"import": {
|
|
80
|
+
"types": "../api/index.d.ts",
|
|
81
|
+
"default": "./api/index.mjs"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=22.0.0"
|
|
87
|
+
},
|
|
88
|
+
"peerDependencies": {
|
|
89
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
90
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
91
|
+
"@frontmcp/sdk": "1.0.0",
|
|
92
|
+
"@frontmcp/utils": "1.0.0",
|
|
93
|
+
"react-router-dom": "^7.0.0",
|
|
94
|
+
"zod": "^4.0.0"
|
|
95
|
+
},
|
|
96
|
+
"peerDependenciesMeta": {
|
|
97
|
+
"react-router-dom": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"zod": {
|
|
101
|
+
"optional": true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"dependencies": {
|
|
105
|
+
"@frontmcp/sdk": "1.0.0",
|
|
106
|
+
"@frontmcp/utils": "1.0.0"
|
|
107
|
+
},
|
|
108
|
+
"devDependencies": {
|
|
109
|
+
"typescript": "^5.9.3"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// libs/react/src/router/useRouterBridge.ts
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { useNavigate, useLocation } from "react-router-dom";
|
|
4
|
+
|
|
5
|
+
// libs/react/src/router/router-bridge.ts
|
|
6
|
+
var currentNavigate = null;
|
|
7
|
+
var currentLocation = null;
|
|
8
|
+
function setNavigate(fn) {
|
|
9
|
+
currentNavigate = fn;
|
|
10
|
+
}
|
|
11
|
+
function setLocation(loc) {
|
|
12
|
+
currentLocation = loc;
|
|
13
|
+
}
|
|
14
|
+
function getNavigate() {
|
|
15
|
+
return currentNavigate;
|
|
16
|
+
}
|
|
17
|
+
function getLocation() {
|
|
18
|
+
return currentLocation;
|
|
19
|
+
}
|
|
20
|
+
function clearBridge() {
|
|
21
|
+
currentNavigate = null;
|
|
22
|
+
currentLocation = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// libs/react/src/router/useRouterBridge.ts
|
|
26
|
+
function useRouterBridge() {
|
|
27
|
+
const navigate = useNavigate();
|
|
28
|
+
const location = useLocation();
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setNavigate(navigate);
|
|
31
|
+
return () => {
|
|
32
|
+
clearBridge();
|
|
33
|
+
};
|
|
34
|
+
}, [navigate]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
setLocation({
|
|
37
|
+
pathname: location.pathname,
|
|
38
|
+
search: location.search,
|
|
39
|
+
hash: location.hash
|
|
40
|
+
});
|
|
41
|
+
}, [location.pathname, location.search, location.hash]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// libs/react/src/router/navigate.tool.ts
|
|
45
|
+
var NavigateTool = class {
|
|
46
|
+
static toolName = "navigate";
|
|
47
|
+
static description = "Navigate to a URL path in the application";
|
|
48
|
+
static inputSchema = {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
path: { type: "string", description: "The URL path to navigate to" },
|
|
52
|
+
replace: { type: "boolean", description: "Replace current history entry instead of pushing" }
|
|
53
|
+
},
|
|
54
|
+
required: ["path"]
|
|
55
|
+
};
|
|
56
|
+
static async execute(args) {
|
|
57
|
+
const navigate = getNavigate();
|
|
58
|
+
if (!navigate) {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: "Router bridge not connected. Ensure useRouterBridge() is called inside a React Router tree."
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
navigate(args.path, { replace: args.replace });
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: "text", text: `Navigated to ${args.path}` }]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// libs/react/src/router/go-back.tool.ts
|
|
76
|
+
var GoBackTool = class {
|
|
77
|
+
static toolName = "go_back";
|
|
78
|
+
static description = "Go back to the previous page in browser history";
|
|
79
|
+
static inputSchema = {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {}
|
|
82
|
+
};
|
|
83
|
+
static async execute() {
|
|
84
|
+
const navigate = getNavigate();
|
|
85
|
+
if (!navigate) {
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: "Router bridge not connected. Ensure useRouterBridge() is called inside a React Router tree."
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
navigate(-1);
|
|
96
|
+
return {
|
|
97
|
+
content: [{ type: "text", text: "Navigated back" }]
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// libs/react/src/router/current-route.resource.ts
|
|
103
|
+
var CurrentRouteResource = class _CurrentRouteResource {
|
|
104
|
+
static uri = "route://current";
|
|
105
|
+
static resourceName = "Current Route";
|
|
106
|
+
static description = "Read the current URL path, search params, and hash";
|
|
107
|
+
static read() {
|
|
108
|
+
const location = getLocation();
|
|
109
|
+
if (!location) {
|
|
110
|
+
return {
|
|
111
|
+
contents: [
|
|
112
|
+
{
|
|
113
|
+
uri: _CurrentRouteResource.uri,
|
|
114
|
+
mimeType: "application/json",
|
|
115
|
+
text: JSON.stringify({
|
|
116
|
+
error: "Router bridge not connected. Ensure useRouterBridge() is called inside a React Router tree."
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
contents: [
|
|
124
|
+
{
|
|
125
|
+
uri: _CurrentRouteResource.uri,
|
|
126
|
+
mimeType: "application/json",
|
|
127
|
+
text: JSON.stringify({
|
|
128
|
+
pathname: location.pathname,
|
|
129
|
+
search: location.search,
|
|
130
|
+
hash: location.hash,
|
|
131
|
+
href: `${location.pathname}${location.search}${location.hash}`
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// libs/react/src/router/router.entries.ts
|
|
140
|
+
function createRouterEntries() {
|
|
141
|
+
return {
|
|
142
|
+
tools: [NavigateTool, GoBackTool],
|
|
143
|
+
resources: [CurrentRouteResource]
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export {
|
|
147
|
+
CurrentRouteResource,
|
|
148
|
+
GoBackTool,
|
|
149
|
+
NavigateTool,
|
|
150
|
+
clearBridge,
|
|
151
|
+
createRouterEntries,
|
|
152
|
+
getLocation,
|
|
153
|
+
getNavigate,
|
|
154
|
+
setLocation,
|
|
155
|
+
setNavigate,
|
|
156
|
+
useRouterBridge
|
|
157
|
+
};
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
// libs/react/src/state/useStoreResource.ts
|
|
2
|
+
import { useContext, useEffect, useRef, useCallback } from "react";
|
|
3
|
+
|
|
4
|
+
// libs/react/src/provider/FrontMcpContext.ts
|
|
5
|
+
import { createContext } from "react";
|
|
6
|
+
|
|
7
|
+
// libs/react/src/components/ComponentRegistry.ts
|
|
8
|
+
var ComponentRegistry = class {
|
|
9
|
+
entries = /* @__PURE__ */ new Map();
|
|
10
|
+
register(uri, component, meta) {
|
|
11
|
+
const name = extractName(uri);
|
|
12
|
+
this.entries.set(uri, { uri, name, component, description: meta?.description });
|
|
13
|
+
}
|
|
14
|
+
registerAll(map) {
|
|
15
|
+
for (const [uri, component] of Object.entries(map)) {
|
|
16
|
+
this.register(uri, component);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
get(uri) {
|
|
20
|
+
return this.entries.get(uri)?.component;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a shorthand name like `'UserCard'` by trying
|
|
24
|
+
* `component://UserCard`, `element://UserCard`, `page://UserCard`.
|
|
25
|
+
*/
|
|
26
|
+
resolve(type) {
|
|
27
|
+
const exact = this.entries.get(type);
|
|
28
|
+
if (exact) return exact.component;
|
|
29
|
+
for (const protocol of ["component://", "element://", "page://"]) {
|
|
30
|
+
const entry = this.entries.get(`${protocol}${type}`);
|
|
31
|
+
if (entry) return entry.component;
|
|
32
|
+
}
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
has(uri) {
|
|
36
|
+
return this.entries.has(uri);
|
|
37
|
+
}
|
|
38
|
+
list() {
|
|
39
|
+
return Array.from(this.entries.values()).map(({ uri, name, description }) => ({
|
|
40
|
+
uri,
|
|
41
|
+
name,
|
|
42
|
+
description
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
clear() {
|
|
46
|
+
this.entries.clear();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function extractName(uri) {
|
|
50
|
+
const idx = uri.indexOf("://");
|
|
51
|
+
return idx >= 0 ? uri.slice(idx + 3) : uri;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// libs/react/src/registry/DynamicRegistry.ts
|
|
55
|
+
var DynamicRegistry = class {
|
|
56
|
+
tools = /* @__PURE__ */ new Map();
|
|
57
|
+
resources = /* @__PURE__ */ new Map();
|
|
58
|
+
toolRefCounts = /* @__PURE__ */ new Map();
|
|
59
|
+
resourceRefCounts = /* @__PURE__ */ new Map();
|
|
60
|
+
listeners = /* @__PURE__ */ new Set();
|
|
61
|
+
version = 0;
|
|
62
|
+
/**
|
|
63
|
+
* Register a dynamic tool. Returns an unregister function
|
|
64
|
+
* suitable for useEffect cleanup.
|
|
65
|
+
*
|
|
66
|
+
* Multiple registrations of the same name are ref-counted:
|
|
67
|
+
* subsequent registrations update the definition but the tool
|
|
68
|
+
* is only removed when every registrant has unregistered.
|
|
69
|
+
*/
|
|
70
|
+
registerTool(def) {
|
|
71
|
+
const existing = this.toolRefCounts.get(def.name) ?? 0;
|
|
72
|
+
this.toolRefCounts.set(def.name, existing + 1);
|
|
73
|
+
this.tools.set(def.name, def);
|
|
74
|
+
if (existing === 0) {
|
|
75
|
+
this.notify();
|
|
76
|
+
}
|
|
77
|
+
let called = false;
|
|
78
|
+
return () => {
|
|
79
|
+
if (called) return;
|
|
80
|
+
called = true;
|
|
81
|
+
this.unregisterTool(def.name);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
unregisterTool(name) {
|
|
85
|
+
const count = this.toolRefCounts.get(name);
|
|
86
|
+
if (count == null) return;
|
|
87
|
+
if (count <= 1) {
|
|
88
|
+
this.toolRefCounts.delete(name);
|
|
89
|
+
this.tools.delete(name);
|
|
90
|
+
this.notify();
|
|
91
|
+
} else {
|
|
92
|
+
this.toolRefCounts.set(name, count - 1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Register a dynamic resource. Returns an unregister function
|
|
97
|
+
* suitable for useEffect cleanup.
|
|
98
|
+
*
|
|
99
|
+
* Multiple registrations of the same URI are ref-counted.
|
|
100
|
+
*/
|
|
101
|
+
registerResource(def) {
|
|
102
|
+
const existing = this.resourceRefCounts.get(def.uri) ?? 0;
|
|
103
|
+
this.resourceRefCounts.set(def.uri, existing + 1);
|
|
104
|
+
this.resources.set(def.uri, def);
|
|
105
|
+
if (existing === 0) {
|
|
106
|
+
this.notify();
|
|
107
|
+
}
|
|
108
|
+
let called = false;
|
|
109
|
+
return () => {
|
|
110
|
+
if (called) return;
|
|
111
|
+
called = true;
|
|
112
|
+
this.unregisterResource(def.uri);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
unregisterResource(uri) {
|
|
116
|
+
const count = this.resourceRefCounts.get(uri);
|
|
117
|
+
if (count == null) return;
|
|
118
|
+
if (count <= 1) {
|
|
119
|
+
this.resourceRefCounts.delete(uri);
|
|
120
|
+
this.resources.delete(uri);
|
|
121
|
+
this.notify();
|
|
122
|
+
} else {
|
|
123
|
+
this.resourceRefCounts.set(uri, count - 1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Update the execute function for an existing tool (for stale closure prevention). */
|
|
127
|
+
updateToolExecute(name, execute) {
|
|
128
|
+
const existing = this.tools.get(name);
|
|
129
|
+
if (existing) {
|
|
130
|
+
existing.execute = execute;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Update the read function for an existing resource and notify subscribers. */
|
|
134
|
+
updateResourceRead(uri, read) {
|
|
135
|
+
const existing = this.resources.get(uri);
|
|
136
|
+
if (existing) {
|
|
137
|
+
existing.read = read;
|
|
138
|
+
this.notify();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
getTools() {
|
|
142
|
+
return [...this.tools.values()];
|
|
143
|
+
}
|
|
144
|
+
getResources() {
|
|
145
|
+
return [...this.resources.values()];
|
|
146
|
+
}
|
|
147
|
+
findTool(name) {
|
|
148
|
+
return this.tools.get(name);
|
|
149
|
+
}
|
|
150
|
+
findResource(uri) {
|
|
151
|
+
return this.resources.get(uri);
|
|
152
|
+
}
|
|
153
|
+
hasTool(name) {
|
|
154
|
+
return this.tools.has(name);
|
|
155
|
+
}
|
|
156
|
+
hasResource(uri) {
|
|
157
|
+
return this.resources.has(uri);
|
|
158
|
+
}
|
|
159
|
+
subscribe(listener) {
|
|
160
|
+
this.listeners.add(listener);
|
|
161
|
+
return () => {
|
|
162
|
+
this.listeners.delete(listener);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
getVersion() {
|
|
166
|
+
return this.version;
|
|
167
|
+
}
|
|
168
|
+
clear() {
|
|
169
|
+
if (this.tools.size === 0 && this.resources.size === 0) return;
|
|
170
|
+
this.tools.clear();
|
|
171
|
+
this.resources.clear();
|
|
172
|
+
this.toolRefCounts.clear();
|
|
173
|
+
this.resourceRefCounts.clear();
|
|
174
|
+
this.notify();
|
|
175
|
+
}
|
|
176
|
+
notify() {
|
|
177
|
+
this.version++;
|
|
178
|
+
this.listeners.forEach((l) => {
|
|
179
|
+
l();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// libs/react/src/provider/FrontMcpContext.ts
|
|
185
|
+
var defaultDynamicRegistry = new DynamicRegistry();
|
|
186
|
+
var FrontMcpContext = createContext({
|
|
187
|
+
name: "default",
|
|
188
|
+
registry: new ComponentRegistry(),
|
|
189
|
+
dynamicRegistry: defaultDynamicRegistry,
|
|
190
|
+
getDynamicRegistry: () => defaultDynamicRegistry,
|
|
191
|
+
connect: async () => {
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// libs/react/src/state/useStoreResource.ts
|
|
196
|
+
var VALID_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
197
|
+
function useStoreResource(options) {
|
|
198
|
+
const { name, getState, subscribe, selectors, actions } = options;
|
|
199
|
+
const { getDynamicRegistry } = useContext(FrontMcpContext);
|
|
200
|
+
const dynamicRegistry = getDynamicRegistry(options.server);
|
|
201
|
+
if (!name || !VALID_NAME_RE.test(name)) {
|
|
202
|
+
throw new Error(`useStoreResource: invalid store name "${name}". Names must match ${VALID_NAME_RE}.`);
|
|
203
|
+
}
|
|
204
|
+
const getStateRef = useRef(getState);
|
|
205
|
+
getStateRef.current = getState;
|
|
206
|
+
const readState = useCallback(
|
|
207
|
+
async () => ({
|
|
208
|
+
contents: [
|
|
209
|
+
{
|
|
210
|
+
uri: `state://${name}`,
|
|
211
|
+
mimeType: "application/json",
|
|
212
|
+
text: JSON.stringify(getStateRef.current() ?? null)
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
}),
|
|
216
|
+
[name]
|
|
217
|
+
);
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
const unregister = dynamicRegistry.registerResource({
|
|
220
|
+
uri: `state://${name}`,
|
|
221
|
+
name: `${name}-state`,
|
|
222
|
+
description: `Full state of ${name} store`,
|
|
223
|
+
mimeType: "application/json",
|
|
224
|
+
read: readState
|
|
225
|
+
});
|
|
226
|
+
const unsubscribe = subscribe(() => {
|
|
227
|
+
dynamicRegistry.updateResourceRead(`state://${name}`, readState);
|
|
228
|
+
});
|
|
229
|
+
return () => {
|
|
230
|
+
unregister();
|
|
231
|
+
unsubscribe();
|
|
232
|
+
};
|
|
233
|
+
}, [dynamicRegistry, name, subscribe, readState]);
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (!selectors) return;
|
|
236
|
+
const cleanups = [];
|
|
237
|
+
const selectorUris = [];
|
|
238
|
+
for (const [key, selector] of Object.entries(selectors)) {
|
|
239
|
+
if (!key || !VALID_NAME_RE.test(key)) {
|
|
240
|
+
throw new Error(`useStoreResource: invalid selector key "${key}". Keys must match ${VALID_NAME_RE}.`);
|
|
241
|
+
}
|
|
242
|
+
const uri = `state://${name}/${key}`;
|
|
243
|
+
const selectorRef = { current: selector };
|
|
244
|
+
const readSelector = async () => ({
|
|
245
|
+
contents: [
|
|
246
|
+
{
|
|
247
|
+
uri,
|
|
248
|
+
mimeType: "application/json",
|
|
249
|
+
text: JSON.stringify(selectorRef.current(getStateRef.current()) ?? null)
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
selectorUris.push({ uri, readSelector });
|
|
254
|
+
cleanups.push(
|
|
255
|
+
dynamicRegistry.registerResource({
|
|
256
|
+
uri,
|
|
257
|
+
name: `${name}-${key}`,
|
|
258
|
+
description: `Selector "${key}" from ${name} store`,
|
|
259
|
+
mimeType: "application/json",
|
|
260
|
+
read: readSelector
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
const unsubscribe = subscribe(() => {
|
|
265
|
+
for (const { uri, readSelector } of selectorUris) {
|
|
266
|
+
dynamicRegistry.updateResourceRead(uri, readSelector);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
cleanups.push(unsubscribe);
|
|
270
|
+
return () => {
|
|
271
|
+
cleanups.forEach((fn) => {
|
|
272
|
+
fn();
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
}, [dynamicRegistry, name, selectors, subscribe]);
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
if (!actions) return;
|
|
278
|
+
const cleanups = [];
|
|
279
|
+
for (const [key, action] of Object.entries(actions)) {
|
|
280
|
+
const toolName = `${name}_${key}`;
|
|
281
|
+
const execute = async (args) => {
|
|
282
|
+
const argsArray = args["args"];
|
|
283
|
+
const result = await (Array.isArray(argsArray) ? action(...argsArray) : action(args));
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, result }) }]
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
cleanups.push(
|
|
289
|
+
dynamicRegistry.registerTool({
|
|
290
|
+
name: toolName,
|
|
291
|
+
description: `Action "${key}" on ${name} store`,
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
args: { type: "array", description: "Arguments to pass to the action" }
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
execute
|
|
299
|
+
})
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return () => {
|
|
303
|
+
cleanups.forEach((fn) => {
|
|
304
|
+
fn();
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
}, [dynamicRegistry, name, actions]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// libs/react/src/state/useReduxResource.ts
|
|
311
|
+
import { useMemo } from "react";
|
|
312
|
+
function useReduxResource(options) {
|
|
313
|
+
const { store, name = "redux", selectors, actions, server } = options;
|
|
314
|
+
const wrappedActions = useMemo(() => {
|
|
315
|
+
if (!actions) return void 0;
|
|
316
|
+
const wrapped = {};
|
|
317
|
+
for (const [key, actionCreator] of Object.entries(actions)) {
|
|
318
|
+
wrapped[key] = (...args) => {
|
|
319
|
+
const action = actionCreator(...args);
|
|
320
|
+
return store.dispatch(action);
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return wrapped;
|
|
324
|
+
}, [actions, store]);
|
|
325
|
+
useStoreResource({
|
|
326
|
+
name,
|
|
327
|
+
getState: store.getState.bind(store),
|
|
328
|
+
subscribe: store.subscribe.bind(store),
|
|
329
|
+
selectors,
|
|
330
|
+
actions: wrappedActions,
|
|
331
|
+
server
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// libs/react/src/state/useValtioResource.ts
|
|
336
|
+
import { useMemo as useMemo2 } from "react";
|
|
337
|
+
function getByPath(obj, path) {
|
|
338
|
+
const parts = path.split(".");
|
|
339
|
+
let current = obj;
|
|
340
|
+
for (const part of parts) {
|
|
341
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
342
|
+
current = current[part];
|
|
343
|
+
}
|
|
344
|
+
return current;
|
|
345
|
+
}
|
|
346
|
+
function useValtioResource(options) {
|
|
347
|
+
const { proxy, subscribe: valtioSubscribe, name = "valtio", paths, mutations, server } = options;
|
|
348
|
+
const selectors = useMemo2(() => {
|
|
349
|
+
if (!paths) return void 0;
|
|
350
|
+
const sels = {};
|
|
351
|
+
for (const [key, path] of Object.entries(paths)) {
|
|
352
|
+
sels[key] = (state) => getByPath(state, path);
|
|
353
|
+
}
|
|
354
|
+
return sels;
|
|
355
|
+
}, [paths]);
|
|
356
|
+
const subscribe = useMemo2(() => (cb) => valtioSubscribe(proxy, cb), [proxy, valtioSubscribe]);
|
|
357
|
+
const actions = useMemo2(() => {
|
|
358
|
+
if (!mutations) return void 0;
|
|
359
|
+
const wrapped = {};
|
|
360
|
+
for (const [key, mutation] of Object.entries(mutations)) {
|
|
361
|
+
wrapped[key] = (...args) => mutation(...args);
|
|
362
|
+
}
|
|
363
|
+
return wrapped;
|
|
364
|
+
}, [mutations]);
|
|
365
|
+
const getState = useMemo2(() => () => JSON.parse(JSON.stringify(proxy)), [proxy]);
|
|
366
|
+
useStoreResource({
|
|
367
|
+
name,
|
|
368
|
+
getState,
|
|
369
|
+
subscribe,
|
|
370
|
+
selectors,
|
|
371
|
+
actions,
|
|
372
|
+
server
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// libs/react/src/state/adapters/reduxAdapter.ts
|
|
377
|
+
function reduxStore(options) {
|
|
378
|
+
const { store, name = "redux", selectors, actions: rawActions } = options;
|
|
379
|
+
let actions;
|
|
380
|
+
if (rawActions) {
|
|
381
|
+
actions = {};
|
|
382
|
+
for (const [key, actionCreator] of Object.entries(rawActions)) {
|
|
383
|
+
actions[key] = (...args) => {
|
|
384
|
+
const action = actionCreator(...args);
|
|
385
|
+
return store.dispatch(action);
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
name,
|
|
391
|
+
getState: store.getState.bind(store),
|
|
392
|
+
subscribe: store.subscribe.bind(store),
|
|
393
|
+
selectors,
|
|
394
|
+
actions
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// libs/react/src/state/adapters/valtioAdapter.ts
|
|
399
|
+
function getByPath2(obj, path) {
|
|
400
|
+
const parts = path.split(".");
|
|
401
|
+
let current = obj;
|
|
402
|
+
for (const part of parts) {
|
|
403
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
404
|
+
current = current[part];
|
|
405
|
+
}
|
|
406
|
+
return current;
|
|
407
|
+
}
|
|
408
|
+
function valtioStore(options) {
|
|
409
|
+
const { proxy, subscribe: valtioSubscribe, name = "valtio", paths, mutations } = options;
|
|
410
|
+
let selectors;
|
|
411
|
+
if (paths) {
|
|
412
|
+
selectors = {};
|
|
413
|
+
for (const [key, path] of Object.entries(paths)) {
|
|
414
|
+
selectors[key] = (state) => getByPath2(state, path);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
let actions;
|
|
418
|
+
if (mutations) {
|
|
419
|
+
actions = {};
|
|
420
|
+
for (const [key, mutation] of Object.entries(mutations)) {
|
|
421
|
+
actions[key] = (...args) => mutation(...args);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
name,
|
|
426
|
+
getState: () => JSON.parse(JSON.stringify(proxy)),
|
|
427
|
+
subscribe: (cb) => valtioSubscribe(proxy, cb),
|
|
428
|
+
selectors,
|
|
429
|
+
actions
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// libs/react/src/state/adapters/createStore.ts
|
|
434
|
+
function createStore(options) {
|
|
435
|
+
return {
|
|
436
|
+
name: options.name,
|
|
437
|
+
getState: options.getState,
|
|
438
|
+
subscribe: options.subscribe,
|
|
439
|
+
selectors: options.selectors,
|
|
440
|
+
actions: options.actions
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
export {
|
|
444
|
+
createStore,
|
|
445
|
+
reduxStore,
|
|
446
|
+
useReduxResource,
|
|
447
|
+
useStoreResource,
|
|
448
|
+
useValtioResource,
|
|
449
|
+
valtioStore
|
|
450
|
+
};
|