medusa-plugin-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/medusa-config.d.ts +1 -0
- package/.medusa/server/medusa-config.js +23 -0
- package/.medusa/server/src/admin/index.js +192 -0
- package/.medusa/server/src/admin/index.mjs +191 -0
- package/.medusa/server/src/api/admin/chat/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/chat/route.js +102 -0
- package/.medusa/server/src/api/admin/mcp/route.d.ts +4 -0
- package/.medusa/server/src/api/admin/mcp/route.js +60 -0
- package/.medusa/server/src/api/middlewares.d.ts +2 -0
- package/.medusa/server/src/api/middlewares.js +24 -0
- package/.medusa/server/src/api/validators.d.ts +24 -0
- package/.medusa/server/src/api/validators.js +11 -0
- package/.medusa/server/src/lib/llm-provider.d.ts +32 -0
- package/.medusa/server/src/lib/llm-provider.js +22 -0
- package/.medusa/server/src/lib/providers/anthropic.d.ts +11 -0
- package/.medusa/server/src/lib/providers/anthropic.js +53 -0
- package/.medusa/server/src/lib/providers/ollama.d.ts +11 -0
- package/.medusa/server/src/lib/providers/ollama.js +39 -0
- package/.medusa/server/src/lib/providers/openai.d.ts +12 -0
- package/.medusa/server/src/lib/providers/openai.js +49 -0
- package/.medusa/server/src/mcp/server.d.ts +3 -0
- package/.medusa/server/src/mcp/server.js +42 -0
- package/.medusa/server/src/mcp/tools/automations.d.ts +3 -0
- package/.medusa/server/src/mcp/tools/automations.js +176 -0
- package/.medusa/server/src/mcp/tools/customers.d.ts +3 -0
- package/.medusa/server/src/mcp/tools/customers.js +72 -0
- package/.medusa/server/src/mcp/tools/inventory.d.ts +3 -0
- package/.medusa/server/src/mcp/tools/inventory.js +70 -0
- package/.medusa/server/src/mcp/tools/orders.d.ts +3 -0
- package/.medusa/server/src/mcp/tools/orders.js +80 -0
- package/.medusa/server/src/mcp/tools/products.d.ts +3 -0
- package/.medusa/server/src/mcp/tools/products.js +72 -0
- package/.medusa/server/src/mcp/tools/query.d.ts +3 -0
- package/.medusa/server/src/mcp/tools/query.js +42 -0
- package/.medusa/server/src/mcp/tools/statistics.d.ts +3 -0
- package/.medusa/server/src/mcp/tools/statistics.js +10 -0
- package/.medusa/server/src/modules/mcp/index.d.ts +7 -0
- package/.medusa/server/src/modules/mcp/index.js +25 -0
- package/.medusa/server/src/modules/mcp/service.d.ts +8 -0
- package/.medusa/server/src/modules/mcp/service.js +15 -0
- package/.medusa/server/src/types.d.ts +7 -0
- package/.medusa/server/src/types.js +3 -0
- package/LICENSE.md +73 -0
- package/package.json +98 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
4
|
+
(0, utils_1.loadEnv)(process.env.NODE_ENV || 'development', process.cwd());
|
|
5
|
+
module.exports = (0, utils_1.defineConfig)({
|
|
6
|
+
projectConfig: {
|
|
7
|
+
redisUrl: process.env.REDIS_URL,
|
|
8
|
+
databaseUrl: process.env.DATABASE_URL,
|
|
9
|
+
http: {
|
|
10
|
+
storeCors: process.env.STORE_CORS || 'http://localhost:5173',
|
|
11
|
+
adminCors: process.env.ADMIN_CORS || 'http://localhost:5173,http://localhost:9000',
|
|
12
|
+
authCors: process.env.AUTH_CORS || 'http://localhost:5173,http://localhost:9000',
|
|
13
|
+
jwtSecret: process.env.JWT_SECRET || 'supersecret',
|
|
14
|
+
cookieSecret: process.env.COOKIE_SECRET || 'supersecret'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
modules: [
|
|
18
|
+
{
|
|
19
|
+
resolve: './src/modules/mcp'
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVkdXNhLWNvbmZpZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL21lZHVzYS1jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxxREFBaUU7QUFFakUsSUFBQSxlQUFPLEVBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLElBQUksYUFBYSxFQUFFLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFBO0FBRTdELE1BQU0sQ0FBQyxPQUFPLEdBQUcsSUFBQSxvQkFBWSxFQUFDO0lBQzdCLGFBQWEsRUFBRTtRQUNkLFFBQVEsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVM7UUFDL0IsV0FBVyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWTtRQUNyQyxJQUFJLEVBQUU7WUFDTCxTQUFTLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksdUJBQXVCO1lBQzVELFNBQVMsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsSUFBSSw2Q0FBNkM7WUFDbEYsUUFBUSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxJQUFJLDZDQUE2QztZQUNoRixTQUFTLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksYUFBYTtZQUNsRCxZQUFZLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLElBQUksYUFBYTtTQUN4RDtLQUNEO0lBQ0QsT0FBTyxFQUFFO1FBQ1I7WUFDQyxPQUFPLEVBQUUsbUJBQW1CO1NBQzVCO0tBQ0Q7Q0FDRCxDQUFDLENBQUEifQ==
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const adminSdk = require("@medusajs/admin-sdk");
|
|
5
|
+
const icons = require("@medusajs/icons");
|
|
6
|
+
const ui = require("@medusajs/ui");
|
|
7
|
+
const reactQuery = require("@tanstack/react-query");
|
|
8
|
+
const Medusa = require("@medusajs/js-sdk");
|
|
9
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
10
|
+
const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
|
|
11
|
+
const sdk = new Medusa__default.default({
|
|
12
|
+
baseUrl: "",
|
|
13
|
+
auth: { type: "session" }
|
|
14
|
+
});
|
|
15
|
+
const useChat = () => {
|
|
16
|
+
return reactQuery.useMutation({
|
|
17
|
+
mutationFn: (messages) => sdk.client.fetch("/admin/chat", {
|
|
18
|
+
method: "POST",
|
|
19
|
+
body: { messages }
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
const ToolCallBlock = ({ toolCall }) => {
|
|
24
|
+
const [expanded, setExpanded] = react.useState(false);
|
|
25
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "my-1", children: [
|
|
26
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
27
|
+
"button",
|
|
28
|
+
{
|
|
29
|
+
onClick: () => setExpanded(!expanded),
|
|
30
|
+
className: "flex items-center gap-1.5 text-xs text-ui-fg-subtle hover:text-ui-fg-base transition-colors",
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: toolCall.name }),
|
|
33
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: expanded ? "▼" : "▶" })
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
),
|
|
37
|
+
expanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 ml-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
38
|
+
ui.CodeBlock,
|
|
39
|
+
{
|
|
40
|
+
snippets: [
|
|
41
|
+
{
|
|
42
|
+
label: `${toolCall.name}(${JSON.stringify(toolCall.args)})`,
|
|
43
|
+
language: "json",
|
|
44
|
+
code: toolCall.result,
|
|
45
|
+
hideLineNumbers: true
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.CodeBlock.Body, { className: "text-xs [&_code]:text-xs max-h-48 overflow-y-auto" })
|
|
49
|
+
}
|
|
50
|
+
) })
|
|
51
|
+
] });
|
|
52
|
+
};
|
|
53
|
+
const ChatMessage = ({ role, content, toolCalls }) => {
|
|
54
|
+
if (role === "user") {
|
|
55
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[80%] self-end rounded-lg px-4 py-2.5 bg-ui-bg-subtle text-ui-fg-base", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: content }) });
|
|
56
|
+
}
|
|
57
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-[90%] self-start flex flex-col gap-1", children: [
|
|
58
|
+
toolCalls && toolCalls.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", children: toolCalls.map((tc, i) => /* @__PURE__ */ jsxRuntime.jsx(ToolCallBlock, { toolCall: tc }, i)) }),
|
|
59
|
+
content && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg px-4 py-2.5 bg-ui-bg-base border border-ui-border-base", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "whitespace-pre-wrap", children: content }) })
|
|
60
|
+
] });
|
|
61
|
+
};
|
|
62
|
+
const config = adminSdk.defineRouteConfig({
|
|
63
|
+
label: "Chat",
|
|
64
|
+
icon: icons.Robot
|
|
65
|
+
});
|
|
66
|
+
const handle = { breadcrumb: () => "Chat" };
|
|
67
|
+
const ChatPage = () => {
|
|
68
|
+
const [messages, setMessages] = react.useState([]);
|
|
69
|
+
const [input, setInput] = react.useState("");
|
|
70
|
+
const messagesEndRef = react.useRef(null);
|
|
71
|
+
const inputRef = react.useRef(null);
|
|
72
|
+
const { mutate: sendChat, isPending } = useChat();
|
|
73
|
+
react.useEffect(() => {
|
|
74
|
+
var _a;
|
|
75
|
+
(_a = messagesEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
76
|
+
}, [messages]);
|
|
77
|
+
const handleSend = () => {
|
|
78
|
+
const text = input.trim();
|
|
79
|
+
if (!text || isPending) return;
|
|
80
|
+
const userMessage = { role: "user", content: text };
|
|
81
|
+
const updatedMessages = [...messages, userMessage];
|
|
82
|
+
setMessages(updatedMessages);
|
|
83
|
+
setInput("");
|
|
84
|
+
const apiMessages = updatedMessages.map(({ role, content }) => ({ role, content }));
|
|
85
|
+
sendChat(apiMessages, {
|
|
86
|
+
onSuccess: (data) => {
|
|
87
|
+
setMessages((prev) => [
|
|
88
|
+
...prev,
|
|
89
|
+
{
|
|
90
|
+
role: "assistant",
|
|
91
|
+
content: data.content,
|
|
92
|
+
toolCalls: data.tool_calls
|
|
93
|
+
}
|
|
94
|
+
]);
|
|
95
|
+
},
|
|
96
|
+
onError: (err) => {
|
|
97
|
+
setMessages((prev) => [
|
|
98
|
+
...prev,
|
|
99
|
+
{
|
|
100
|
+
role: "assistant",
|
|
101
|
+
content: `Error: ${err instanceof Error ? err.message : "Something went wrong"}`
|
|
102
|
+
}
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
const handleKeyDown = (e) => {
|
|
108
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
handleSend();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col h-[calc(100vh-120px)] p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col flex-1 p-0 overflow-hidden", children: [
|
|
114
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 border-b border-ui-border-base", children: [
|
|
115
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Chat" }),
|
|
116
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle mt-1", children: "Ask questions about your store data or manage automations using natural language." })
|
|
117
|
+
] }),
|
|
118
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto px-6 py-4 flex flex-col gap-3", children: [
|
|
119
|
+
messages.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: 'Send a message to get started. Try "Show me recent orders" or "List my automations."' }) }),
|
|
120
|
+
messages.map((msg, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
121
|
+
ChatMessage,
|
|
122
|
+
{
|
|
123
|
+
role: msg.role,
|
|
124
|
+
content: msg.content,
|
|
125
|
+
toolCalls: msg.toolCalls
|
|
126
|
+
},
|
|
127
|
+
i
|
|
128
|
+
)),
|
|
129
|
+
isPending && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "self-start", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted animate-pulse", children: "Thinking..." }) }),
|
|
130
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
|
|
131
|
+
] }),
|
|
132
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4 border-t border-ui-border-base", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
133
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
134
|
+
ui.Input,
|
|
135
|
+
{
|
|
136
|
+
ref: inputRef,
|
|
137
|
+
value: input,
|
|
138
|
+
onChange: (e) => setInput(e.target.value),
|
|
139
|
+
onKeyDown: handleKeyDown,
|
|
140
|
+
placeholder: "Ask a question...",
|
|
141
|
+
disabled: isPending,
|
|
142
|
+
className: "flex-1"
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
146
|
+
ui.Button,
|
|
147
|
+
{
|
|
148
|
+
onClick: handleSend,
|
|
149
|
+
disabled: !input.trim() || isPending,
|
|
150
|
+
isLoading: isPending,
|
|
151
|
+
children: "Send"
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
] }) })
|
|
155
|
+
] }) });
|
|
156
|
+
};
|
|
157
|
+
const widgetModule = { widgets: [] };
|
|
158
|
+
const routeModule = {
|
|
159
|
+
routes: [
|
|
160
|
+
{
|
|
161
|
+
Component: ChatPage,
|
|
162
|
+
path: "/chat",
|
|
163
|
+
handle
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
};
|
|
167
|
+
const menuItemModule = {
|
|
168
|
+
menuItems: [
|
|
169
|
+
{
|
|
170
|
+
label: config.label,
|
|
171
|
+
icon: config.icon,
|
|
172
|
+
path: "/chat",
|
|
173
|
+
nested: void 0,
|
|
174
|
+
rank: void 0,
|
|
175
|
+
translationNs: void 0
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
};
|
|
179
|
+
const formModule = { customFields: {} };
|
|
180
|
+
const displayModule = {
|
|
181
|
+
displays: {}
|
|
182
|
+
};
|
|
183
|
+
const i18nModule = { resources: {} };
|
|
184
|
+
const plugin = {
|
|
185
|
+
widgetModule,
|
|
186
|
+
routeModule,
|
|
187
|
+
menuItemModule,
|
|
188
|
+
formModule,
|
|
189
|
+
displayModule,
|
|
190
|
+
i18nModule
|
|
191
|
+
};
|
|
192
|
+
module.exports = plugin;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect } from "react";
|
|
3
|
+
import { defineRouteConfig } from "@medusajs/admin-sdk";
|
|
4
|
+
import { Robot } from "@medusajs/icons";
|
|
5
|
+
import { Text, Badge, CodeBlock, Container, Heading, Input, Button } from "@medusajs/ui";
|
|
6
|
+
import { useMutation } from "@tanstack/react-query";
|
|
7
|
+
import Medusa from "@medusajs/js-sdk";
|
|
8
|
+
const sdk = new Medusa({
|
|
9
|
+
baseUrl: "",
|
|
10
|
+
auth: { type: "session" }
|
|
11
|
+
});
|
|
12
|
+
const useChat = () => {
|
|
13
|
+
return useMutation({
|
|
14
|
+
mutationFn: (messages) => sdk.client.fetch("/admin/chat", {
|
|
15
|
+
method: "POST",
|
|
16
|
+
body: { messages }
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
const ToolCallBlock = ({ toolCall }) => {
|
|
21
|
+
const [expanded, setExpanded] = useState(false);
|
|
22
|
+
return /* @__PURE__ */ jsxs("div", { className: "my-1", children: [
|
|
23
|
+
/* @__PURE__ */ jsxs(
|
|
24
|
+
"button",
|
|
25
|
+
{
|
|
26
|
+
onClick: () => setExpanded(!expanded),
|
|
27
|
+
className: "flex items-center gap-1.5 text-xs text-ui-fg-subtle hover:text-ui-fg-base transition-colors",
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: toolCall.name }),
|
|
30
|
+
/* @__PURE__ */ jsx("span", { children: expanded ? "▼" : "▶" })
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
),
|
|
34
|
+
expanded && /* @__PURE__ */ jsx("div", { className: "mt-1 ml-2", children: /* @__PURE__ */ jsx(
|
|
35
|
+
CodeBlock,
|
|
36
|
+
{
|
|
37
|
+
snippets: [
|
|
38
|
+
{
|
|
39
|
+
label: `${toolCall.name}(${JSON.stringify(toolCall.args)})`,
|
|
40
|
+
language: "json",
|
|
41
|
+
code: toolCall.result,
|
|
42
|
+
hideLineNumbers: true
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
children: /* @__PURE__ */ jsx(CodeBlock.Body, { className: "text-xs [&_code]:text-xs max-h-48 overflow-y-auto" })
|
|
46
|
+
}
|
|
47
|
+
) })
|
|
48
|
+
] });
|
|
49
|
+
};
|
|
50
|
+
const ChatMessage = ({ role, content, toolCalls }) => {
|
|
51
|
+
if (role === "user") {
|
|
52
|
+
return /* @__PURE__ */ jsx("div", { className: "max-w-[80%] self-end rounded-lg px-4 py-2.5 bg-ui-bg-subtle text-ui-fg-base", children: /* @__PURE__ */ jsx(Text, { size: "small", children: content }) });
|
|
53
|
+
}
|
|
54
|
+
return /* @__PURE__ */ jsxs("div", { className: "max-w-[90%] self-start flex flex-col gap-1", children: [
|
|
55
|
+
toolCalls && toolCalls.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: toolCalls.map((tc, i) => /* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, i)) }),
|
|
56
|
+
content && /* @__PURE__ */ jsx("div", { className: "rounded-lg px-4 py-2.5 bg-ui-bg-base border border-ui-border-base", children: /* @__PURE__ */ jsx(Text, { size: "small", className: "whitespace-pre-wrap", children: content }) })
|
|
57
|
+
] });
|
|
58
|
+
};
|
|
59
|
+
const config = defineRouteConfig({
|
|
60
|
+
label: "Chat",
|
|
61
|
+
icon: Robot
|
|
62
|
+
});
|
|
63
|
+
const handle = { breadcrumb: () => "Chat" };
|
|
64
|
+
const ChatPage = () => {
|
|
65
|
+
const [messages, setMessages] = useState([]);
|
|
66
|
+
const [input, setInput] = useState("");
|
|
67
|
+
const messagesEndRef = useRef(null);
|
|
68
|
+
const inputRef = useRef(null);
|
|
69
|
+
const { mutate: sendChat, isPending } = useChat();
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
var _a;
|
|
72
|
+
(_a = messagesEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
73
|
+
}, [messages]);
|
|
74
|
+
const handleSend = () => {
|
|
75
|
+
const text = input.trim();
|
|
76
|
+
if (!text || isPending) return;
|
|
77
|
+
const userMessage = { role: "user", content: text };
|
|
78
|
+
const updatedMessages = [...messages, userMessage];
|
|
79
|
+
setMessages(updatedMessages);
|
|
80
|
+
setInput("");
|
|
81
|
+
const apiMessages = updatedMessages.map(({ role, content }) => ({ role, content }));
|
|
82
|
+
sendChat(apiMessages, {
|
|
83
|
+
onSuccess: (data) => {
|
|
84
|
+
setMessages((prev) => [
|
|
85
|
+
...prev,
|
|
86
|
+
{
|
|
87
|
+
role: "assistant",
|
|
88
|
+
content: data.content,
|
|
89
|
+
toolCalls: data.tool_calls
|
|
90
|
+
}
|
|
91
|
+
]);
|
|
92
|
+
},
|
|
93
|
+
onError: (err) => {
|
|
94
|
+
setMessages((prev) => [
|
|
95
|
+
...prev,
|
|
96
|
+
{
|
|
97
|
+
role: "assistant",
|
|
98
|
+
content: `Error: ${err instanceof Error ? err.message : "Something went wrong"}`
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
const handleKeyDown = (e) => {
|
|
105
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
handleSend();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col h-[calc(100vh-120px)] p-4", children: /* @__PURE__ */ jsxs(Container, { className: "flex flex-col flex-1 p-0 overflow-hidden", children: [
|
|
111
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-b border-ui-border-base", children: [
|
|
112
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", children: "Chat" }),
|
|
113
|
+
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle mt-1", children: "Ask questions about your store data or manage automations using natural language." })
|
|
114
|
+
] }),
|
|
115
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto px-6 py-4 flex flex-col gap-3", children: [
|
|
116
|
+
messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted", children: 'Send a message to get started. Try "Show me recent orders" or "List my automations."' }) }),
|
|
117
|
+
messages.map((msg, i) => /* @__PURE__ */ jsx(
|
|
118
|
+
ChatMessage,
|
|
119
|
+
{
|
|
120
|
+
role: msg.role,
|
|
121
|
+
content: msg.content,
|
|
122
|
+
toolCalls: msg.toolCalls
|
|
123
|
+
},
|
|
124
|
+
i
|
|
125
|
+
)),
|
|
126
|
+
isPending && /* @__PURE__ */ jsx("div", { className: "self-start", children: /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted animate-pulse", children: "Thinking..." }) }),
|
|
127
|
+
/* @__PURE__ */ jsx("div", { ref: messagesEndRef })
|
|
128
|
+
] }),
|
|
129
|
+
/* @__PURE__ */ jsx("div", { className: "px-6 py-4 border-t border-ui-border-base", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
130
|
+
/* @__PURE__ */ jsx(
|
|
131
|
+
Input,
|
|
132
|
+
{
|
|
133
|
+
ref: inputRef,
|
|
134
|
+
value: input,
|
|
135
|
+
onChange: (e) => setInput(e.target.value),
|
|
136
|
+
onKeyDown: handleKeyDown,
|
|
137
|
+
placeholder: "Ask a question...",
|
|
138
|
+
disabled: isPending,
|
|
139
|
+
className: "flex-1"
|
|
140
|
+
}
|
|
141
|
+
),
|
|
142
|
+
/* @__PURE__ */ jsx(
|
|
143
|
+
Button,
|
|
144
|
+
{
|
|
145
|
+
onClick: handleSend,
|
|
146
|
+
disabled: !input.trim() || isPending,
|
|
147
|
+
isLoading: isPending,
|
|
148
|
+
children: "Send"
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
] }) })
|
|
152
|
+
] }) });
|
|
153
|
+
};
|
|
154
|
+
const widgetModule = { widgets: [] };
|
|
155
|
+
const routeModule = {
|
|
156
|
+
routes: [
|
|
157
|
+
{
|
|
158
|
+
Component: ChatPage,
|
|
159
|
+
path: "/chat",
|
|
160
|
+
handle
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
const menuItemModule = {
|
|
165
|
+
menuItems: [
|
|
166
|
+
{
|
|
167
|
+
label: config.label,
|
|
168
|
+
icon: config.icon,
|
|
169
|
+
path: "/chat",
|
|
170
|
+
nested: void 0,
|
|
171
|
+
rank: void 0,
|
|
172
|
+
translationNs: void 0
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
const formModule = { customFields: {} };
|
|
177
|
+
const displayModule = {
|
|
178
|
+
displays: {}
|
|
179
|
+
};
|
|
180
|
+
const i18nModule = { resources: {} };
|
|
181
|
+
const plugin = {
|
|
182
|
+
widgetModule,
|
|
183
|
+
routeModule,
|
|
184
|
+
menuItemModule,
|
|
185
|
+
formModule,
|
|
186
|
+
displayModule,
|
|
187
|
+
i18nModule
|
|
188
|
+
};
|
|
189
|
+
export {
|
|
190
|
+
plugin as default
|
|
191
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { AuthenticatedMedusaRequest, MedusaResponse } from '@medusajs/framework/http';
|
|
2
|
+
import { AdminPostChatType } from '../../validators';
|
|
3
|
+
export declare const POST: (req: AuthenticatedMedusaRequest<AdminPostChatType>, res: MedusaResponse) => Promise<MedusaResponse | undefined>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POST = void 0;
|
|
4
|
+
const zod_to_json_schema_1 = require("zod-to-json-schema");
|
|
5
|
+
const server_1 = require("../../../mcp/server");
|
|
6
|
+
const llm_provider_1 = require("../../../lib/llm-provider");
|
|
7
|
+
const mcp_1 = require("../../../modules/mcp");
|
|
8
|
+
const DEFAULT_SYSTEM_PROMPT = `You are a helpful assistant for a Medusa commerce store admin. You have access to tools that let you query store data (orders, customers, products, inventory) and manage automations. Use the tools to answer questions with real data. Be concise and helpful. When displaying data, format it clearly.`;
|
|
9
|
+
const POST = async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const mcpService = req.scope.resolve(mcp_1.MCP_MODULE);
|
|
12
|
+
const options = mcpService.getOptions();
|
|
13
|
+
if (!options?.provider) {
|
|
14
|
+
return res.status(500).json({
|
|
15
|
+
error: 'MCP plugin options not configured. Set provider, model, and apiKey in medusa-config.ts.'
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (options.provider !== 'ollama' && !options.apiKey) {
|
|
19
|
+
return res.status(500).json({
|
|
20
|
+
error: `API key required for ${options.provider} provider. Set MCP_LLM_API_KEY in your environment.`
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const provider = (0, llm_provider_1.createProvider)(options);
|
|
24
|
+
const mcpServer = await (0, server_1.createMcpServer)(req.scope);
|
|
25
|
+
// Extract tool definitions from MCP server internals
|
|
26
|
+
const registeredTools = mcpServer._registeredTools;
|
|
27
|
+
const toolDefs = [];
|
|
28
|
+
const toolHandlers = new Map();
|
|
29
|
+
for (const [name, tool] of Object.entries(registeredTools)) {
|
|
30
|
+
const jsonSchema = tool.inputSchema
|
|
31
|
+
? (0, zod_to_json_schema_1.zodToJsonSchema)(tool.inputSchema)
|
|
32
|
+
: { type: 'object', properties: {} };
|
|
33
|
+
toolDefs.push({
|
|
34
|
+
name,
|
|
35
|
+
description: tool.description ?? '',
|
|
36
|
+
inputSchema: jsonSchema
|
|
37
|
+
});
|
|
38
|
+
toolHandlers.set(name, tool.handler);
|
|
39
|
+
}
|
|
40
|
+
const systemPrompt = options.systemPrompt
|
|
41
|
+
? `${DEFAULT_SYSTEM_PROMPT}\n\n${options.systemPrompt}`
|
|
42
|
+
: DEFAULT_SYSTEM_PROMPT;
|
|
43
|
+
// Build conversation with tool-use loop
|
|
44
|
+
let messages = [...req.body.messages];
|
|
45
|
+
const allToolCalls = [];
|
|
46
|
+
const MAX_TOOL_ROUNDS = 10;
|
|
47
|
+
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
48
|
+
const response = await provider.chat({
|
|
49
|
+
messages,
|
|
50
|
+
tools: toolDefs,
|
|
51
|
+
systemPrompt
|
|
52
|
+
});
|
|
53
|
+
if (response.toolCalls.length === 0) {
|
|
54
|
+
return res.json({
|
|
55
|
+
role: 'assistant',
|
|
56
|
+
content: response.content,
|
|
57
|
+
tool_calls: allToolCalls.length > 0 ? allToolCalls : undefined
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Execute each tool call via the handler directly
|
|
61
|
+
const toolResults = [];
|
|
62
|
+
for (const tc of response.toolCalls) {
|
|
63
|
+
let result;
|
|
64
|
+
try {
|
|
65
|
+
const handler = toolHandlers.get(tc.name);
|
|
66
|
+
if (!handler)
|
|
67
|
+
throw new Error(`Unknown tool: ${tc.name}`);
|
|
68
|
+
const mcpResult = await handler(tc.args);
|
|
69
|
+
result = mcpResult?.content?.map((c) => c.text ?? JSON.stringify(c)).join('\n') ?? 'No result';
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
73
|
+
}
|
|
74
|
+
toolResults.push(result);
|
|
75
|
+
allToolCalls.push({ name: tc.name, args: tc.args, result });
|
|
76
|
+
}
|
|
77
|
+
const toolSummary = response.toolCalls.map((tc, i) => `[Tool: ${tc.name}]\nArgs: ${JSON.stringify(tc.args)}\nResult: ${toolResults[i]}`).join('\n\n');
|
|
78
|
+
if (response.content) {
|
|
79
|
+
messages.push({ role: 'assistant', content: response.content });
|
|
80
|
+
}
|
|
81
|
+
messages.push({
|
|
82
|
+
role: 'user',
|
|
83
|
+
content: `[Tool results]\n${toolSummary}`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return res.json({
|
|
87
|
+
role: 'assistant',
|
|
88
|
+
content: 'I reached the maximum number of tool calls. Here is what I found so far.',
|
|
89
|
+
tool_calls: allToolCalls.length > 0 ? allToolCalls : undefined
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.error('[Chat] Error:', err);
|
|
94
|
+
if (!res.headersSent) {
|
|
95
|
+
res.status(500).json({
|
|
96
|
+
error: err instanceof Error ? err.message : 'Internal server error'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
exports.POST = POST;
|
|
102
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL2NoYXQvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EsMkRBQW9EO0FBRXBELGdEQUFxRDtBQUNyRCw0REFBaUc7QUFDakcsOENBQWlEO0FBR2pELE1BQU0scUJBQXFCLEdBQUcsMlNBQTJTLENBQUE7QUFFbFUsTUFBTSxJQUFJLEdBQUcsS0FBSyxFQUN4QixHQUFrRCxFQUNsRCxHQUFtQixFQUNsQixFQUFFO0lBQ0gsSUFBSSxDQUFDO1FBQ0osTUFBTSxVQUFVLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsZ0JBQVUsQ0FBZSxDQUFBO1FBQzlELE1BQU0sT0FBTyxHQUFHLFVBQVUsQ0FBQyxVQUFVLEVBQUUsQ0FBQTtRQUV2QyxJQUFJLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxDQUFDO1lBQ3hCLE9BQU8sR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUM7Z0JBQzNCLEtBQUssRUFBRSx5RkFBeUY7YUFDaEcsQ0FBQyxDQUFBO1FBQ0gsQ0FBQztRQUVELElBQUksT0FBTyxDQUFDLFFBQVEsS0FBSyxRQUFRLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDdEQsT0FBTyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztnQkFDM0IsS0FBSyxFQUFFLHdCQUF3QixPQUFPLENBQUMsUUFBUSxxREFBcUQ7YUFDcEcsQ0FBQyxDQUFBO1FBQ0gsQ0FBQztRQUVELE1BQU0sUUFBUSxHQUFHLElBQUEsNkJBQWMsRUFBQyxPQUFPLENBQUMsQ0FBQTtRQUN4QyxNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUEsd0JBQWUsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUE7UUFFbEQscURBQXFEO1FBQ3JELE1BQU0sZUFBZSxH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
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AuthenticatedMedusaRequest, MedusaResponse } from '@medusajs/framework/http';
|
|
2
|
+
export declare const POST: (req: AuthenticatedMedusaRequest, res: MedusaResponse) => Promise<void>;
|
|
3
|
+
export declare const GET: (_req: AuthenticatedMedusaRequest, res: MedusaResponse) => Promise<void>;
|
|
4
|
+
export declare const DELETE: (_req: AuthenticatedMedusaRequest, res: MedusaResponse) => Promise<void>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DELETE = exports.GET = exports.POST = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const server_1 = require("../../../mcp/server");
|
|
8
|
+
// Store transports by session ID for stateful MCP connections
|
|
9
|
+
const sessions = {};
|
|
10
|
+
const POST = async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
13
|
+
let transport;
|
|
14
|
+
if (sessionId && sessions[sessionId]) {
|
|
15
|
+
// Reuse existing session transport
|
|
16
|
+
transport = sessions[sessionId];
|
|
17
|
+
}
|
|
18
|
+
else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) {
|
|
19
|
+
// New initialization request
|
|
20
|
+
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
21
|
+
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
|
|
22
|
+
enableJsonResponse: true,
|
|
23
|
+
onsessioninitialized: (id) => {
|
|
24
|
+
sessions[id] = transport;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const server = await (0, server_1.createMcpServer)(req.scope);
|
|
28
|
+
await server.connect(transport);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
res.status(400).json({
|
|
32
|
+
jsonrpc: '2.0',
|
|
33
|
+
error: { code: -32000, message: 'Bad Request: No valid session. Send initialize first.' },
|
|
34
|
+
id: null
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
await transport.handleRequest(req, res, req.body);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error('[MCP] Error:', err);
|
|
42
|
+
if (!res.headersSent) {
|
|
43
|
+
res.status(500).json({
|
|
44
|
+
jsonrpc: '2.0',
|
|
45
|
+
error: { code: -32603, message: 'Internal server error' },
|
|
46
|
+
id: null
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
exports.POST = POST;
|
|
52
|
+
const GET = async (_req, res) => {
|
|
53
|
+
res.writeHead(405).end(JSON.stringify({ error: 'Method not allowed' }));
|
|
54
|
+
};
|
|
55
|
+
exports.GET = GET;
|
|
56
|
+
const DELETE = async (_req, res) => {
|
|
57
|
+
res.writeHead(405).end(JSON.stringify({ error: 'Method not allowed' }));
|
|
58
|
+
};
|
|
59
|
+
exports.DELETE = DELETE;
|
|
60
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL21jcC9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBd0M7QUFFeEMsMEZBQWtHO0FBQ2xHLGlFQUF3RTtBQUN4RSxnREFBcUQ7QUFFckQsOERBQThEO0FBQzlELE1BQU0sUUFBUSxHQUFrRCxFQUFFLENBQUE7QUFFM0QsTUFBTSxJQUFJLEdBQUcsS0FBSyxFQUFFLEdBQStCLEVBQUUsR0FBbUIsRUFBRSxFQUFFO0lBQ2xGLElBQUksQ0FBQztRQUNKLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQXVCLENBQUE7UUFDckUsSUFBSSxTQUF3QyxDQUFBO1FBRTVDLElBQUksU0FBUyxJQUFJLFFBQVEsQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO1lBQ3RDLG1DQUFtQztZQUNuQyxTQUFTLEdBQUcsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFBO1FBQ2hDLENBQUM7YUFBTSxJQUFJLENBQUMsU0FBUyxJQUFJLElBQUEsOEJBQW1CLEVBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDeEQsNkJBQTZCO1lBQzdCLFNBQVMsR0FBRyxJQUFJLGlEQUE2QixDQUFDO2dCQUM3QyxrQkFBa0IsRUFBRSxHQUFHLEVBQUUsQ0FBQyxJQUFBLHdCQUFVLEdBQUU7Z0JBQ3RDLGtCQUFrQixFQUFFLElBQUk7Z0JBQ3hCLG9CQUFvQixFQUFFLENBQUMsRUFBRSxFQUFFLEVBQUU7b0JBQzVCLFFBQVEsQ0FBQyxFQUFFLENBQUMsR0FBRyxTQUFTLENBQUE7Z0JBQ3pCLENBQUM7YUFDRCxDQUFDLENBQUE7WUFFRixNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUEsd0JBQWUsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUE7WUFDL0MsTUFBTSxNQUFNLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFBO1FBQ2hDLENBQUM7YUFBTSxDQUFDO1lBQ1AsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUM7Z0JBQ3BCLE9BQU8sRUFBRSxLQUFLO2dCQUNkLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsdURBQXVELEVBQUU7Z0JBQ3pGLEVBQUUsRUFBRSxJQUFJO2FBQ1IsQ0FBQyxDQUFBO1lBQ0YsT0FBTTtRQUNQLENBQUM7UUFFRCxNQUFNLFNBQVMsQ0FBQyxhQUFhLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDbEQsQ0FBQztJQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7UUFDZCxPQUFPLENBQUMsS0FBSyxDQUFDLGNBQWMsRUFBRSxHQUFHLENBQUMsQ0FBQTtRQUNsQyxJQUFJLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQ3RCLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO2dCQUNwQixPQUFPLEVBQUUsS0FBSztnQkFDZCxLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLHVCQUF1QixFQUFFO2dCQUN6RCxFQUFFLEVBQUUsSUFBSTthQUNSLENBQUMsQ0FBQTtRQUNILENBQUM7SUFDRixDQUFDO0FBQ0YsQ0FBQyxDQUFBO0FBeENZLFFBQUEsSUFBSSxRQXdDaEI7QUFFTSxNQUFNLEdBQUcsR0FBRyxLQUFLLEVBQUUsSUFBZ0MsRUFBRSxHQUFtQixFQUFFLEVBQUU7SUFDbEYsR0FBRyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxvQkFBb0IsRUFBRSxDQUFDLENBQUMsQ0FBQTtBQUN4RSxDQUFDLENBQUE7QUFGWSxRQUFBLEdBQUcsT0FFZjtBQUVNLE1BQU0sTUFBTSxHQUFHLEtBQUssRUFBRSxJQUFnQyxFQUFFLEdBQW1CLEVBQUUsRUFBRTtJQUNyRixHQUFHLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsS0FBSyxFQUFFLG9CQUFvQixFQUFFLENBQUMsQ0FBQyxDQUFBO0FBQ3hFLENBQUMsQ0FBQTtBQUZZLFFBQUEsTUFBTSxVQUVsQiJ9
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const http_1 = require("@medusajs/framework/http");
|
|
4
|
+
const validators_1 = require("./validators");
|
|
5
|
+
function exposeMcpHeaders(req, res, next) {
|
|
6
|
+
res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id');
|
|
7
|
+
res.setHeader('Access-Control-Allow-Headers', [res.getHeader('Access-Control-Allow-Headers') ?? '', 'mcp-session-id']
|
|
8
|
+
.filter(Boolean)
|
|
9
|
+
.join(', '));
|
|
10
|
+
next();
|
|
11
|
+
}
|
|
12
|
+
exports.default = (0, http_1.defineMiddlewares)([
|
|
13
|
+
{
|
|
14
|
+
matcher: '/admin/mcp',
|
|
15
|
+
method: ['GET', 'POST', 'DELETE'],
|
|
16
|
+
middlewares: [exposeMcpHeaders]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
matcher: '/admin/chat',
|
|
20
|
+
method: ['POST'],
|
|
21
|
+
middlewares: [(0, http_1.validateAndTransformBody)(validators_1.AdminPostChat)]
|
|
22
|
+
}
|
|
23
|
+
]);
|
|
24
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBpL21pZGRsZXdhcmVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsbURBS2lDO0FBRWpDLDZDQUE0QztBQUU1QyxTQUFTLGdCQUFnQixDQUN4QixHQUFrQixFQUNsQixHQUFtQixFQUNuQixJQUFrQjtJQUVsQixHQUFHLENBQUMsU0FBUyxDQUFDLCtCQUErQixFQUFFLGdCQUFnQixDQUFDLENBQUE7SUFDaEUsR0FBRyxDQUFDLFNBQVMsQ0FDWiw4QkFBOEIsRUFDOUIsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLDhCQUE4QixDQUFDLElBQUksRUFBRSxFQUFFLGdCQUFnQixDQUFDO1NBQ3JFLE1BQU0sQ0FBQyxPQUFPLENBQUM7U0FDZixJQUFJLENBQUMsSUFBSSxDQUFDLENBQ1osQ0FBQTtJQUNELElBQUksRUFBRSxDQUFBO0FBQ1AsQ0FBQztBQUVELGtCQUFlLElBQUEsd0JBQWlCLEVBQUM7SUFDaEM7UUFDQyxPQUFPLEVBQUUsWUFBWTtRQUNyQixNQUFNLEVBQUUsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQztRQUNqQyxXQUFXLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQztLQUMvQjtJQUNEO1FBQ0MsT0FBTyxFQUFFLGFBQWE7UUFDdEIsTUFBTSxFQUFFLENBQUMsTUFBTSxDQUFDO1FBQ2hCLFdBQVcsRUFBRSxDQUFDLElBQUEsK0JBQXdCLEVBQUMsMEJBQWEsQ0FBQyxDQUFDO0tBQ3REO0NBQ0QsQ0FBQyxDQUFBIn0=
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from '@medusajs/framework/zod';
|
|
2
|
+
export declare const AdminPostChat: z.ZodObject<{
|
|
3
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
4
|
+
role: z.ZodEnum<["user", "assistant"]>;
|
|
5
|
+
content: z.ZodString;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
role: "user" | "assistant";
|
|
8
|
+
content: string;
|
|
9
|
+
}, {
|
|
10
|
+
role: "user" | "assistant";
|
|
11
|
+
content: string;
|
|
12
|
+
}>, "many">;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
messages: {
|
|
15
|
+
role: "user" | "assistant";
|
|
16
|
+
content: string;
|
|
17
|
+
}[];
|
|
18
|
+
}, {
|
|
19
|
+
messages: {
|
|
20
|
+
role: "user" | "assistant";
|
|
21
|
+
content: string;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
24
|
+
export type AdminPostChatType = z.infer<typeof AdminPostChat>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AdminPostChat = void 0;
|
|
4
|
+
const zod_1 = require("@medusajs/framework/zod");
|
|
5
|
+
exports.AdminPostChat = zod_1.z.object({
|
|
6
|
+
messages: zod_1.z.array(zod_1.z.object({
|
|
7
|
+
role: zod_1.z.enum(['user', 'assistant']),
|
|
8
|
+
content: zod_1.z.string()
|
|
9
|
+
})).min(1)
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9ycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hcGkvdmFsaWRhdG9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxpREFBMkM7QUFFOUIsUUFBQSxhQUFhLEdBQUcsT0FBQyxDQUFDLE1BQU0sQ0FBQztJQUNyQyxRQUFRLEVBQUUsT0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFDLENBQUMsTUFBTSxDQUFDO1FBQzFCLElBQUksRUFBRSxPQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxFQUFFLFdBQVcsQ0FBQyxDQUFDO1FBQ25DLE9BQU8sRUFBRSxPQUFDLENBQUMsTUFBTSxFQUFFO0tBQ25CLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7Q0FDVixDQUFDLENBQUEifQ==
|