@yh-ui/ai-sdk 0.1.21

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.
@@ -0,0 +1,234 @@
1
+ import { ref, onUnmounted } from "vue";
2
+ export class MCPServer {
3
+ config;
4
+ tools = /* @__PURE__ */ new Map();
5
+ requestHandlers = /* @__PURE__ */ new Map();
6
+ isRunning = false;
7
+ constructor(options) {
8
+ this.config = options.config;
9
+ if (options.tools) {
10
+ for (const tool of options.tools) {
11
+ this.tools.set(tool.name, tool);
12
+ }
13
+ }
14
+ this.registerRequestHandler("initialize", this.handleInitialize.bind(this));
15
+ this.registerRequestHandler("tools/list", this.handleToolsList.bind(this));
16
+ this.registerRequestHandler("tools/call", this.handleToolsCall.bind(this));
17
+ this.registerRequestHandler("ping", this.handlePing.bind(this));
18
+ options.onStart?.(this);
19
+ }
20
+ registerRequestHandler(method, handler) {
21
+ this.requestHandlers.set(method, handler);
22
+ }
23
+ generateId() {
24
+ return Math.random().toString(36).substring(2, 9);
25
+ }
26
+ // ── Request Handlers ─────────────────────────────────────────────────────
27
+ handleInitialize(params) {
28
+ const initParams = params;
29
+ return {
30
+ protocolVersion: initParams?.protocolVersion || "2024-11-05",
31
+ capabilities: {
32
+ tools: {}
33
+ },
34
+ serverInfo: {
35
+ name: this.config.name,
36
+ version: this.config.version
37
+ }
38
+ };
39
+ }
40
+ handleToolsList() {
41
+ const tools = Array.from(this.tools.values()).map((tool) => ({
42
+ name: tool.name,
43
+ description: tool.description,
44
+ inputSchema: tool.inputSchema
45
+ }));
46
+ return { tools };
47
+ }
48
+ async handleToolsCall(params) {
49
+ const callParams = params;
50
+ if (!callParams?.name) {
51
+ throw new Error("Missing tool name");
52
+ }
53
+ const tool = this.tools.get(callParams.name);
54
+ if (!tool) {
55
+ throw new Error(`Tool not found: ${callParams.name}`);
56
+ }
57
+ try {
58
+ const result = await tool.execute(callParams.arguments || {});
59
+ return { content: result };
60
+ } catch (err) {
61
+ const errorMessage = err instanceof Error ? err.message : String(err);
62
+ return {
63
+ content: [{ type: "text", text: errorMessage }],
64
+ isError: true
65
+ };
66
+ }
67
+ }
68
+ handlePing() {
69
+ return { pong: true };
70
+ }
71
+ // ── Public API ───────────────────────────────────────────────────────────
72
+ /** Add a tool to the server */
73
+ addTool(tool) {
74
+ this.tools.set(tool.name, tool);
75
+ }
76
+ /** Remove a tool from the server */
77
+ removeTool(name) {
78
+ return this.tools.delete(name);
79
+ }
80
+ /** Get all tools */
81
+ getTools() {
82
+ return Array.from(this.tools.values());
83
+ }
84
+ /** Check if server is running */
85
+ getRunning() {
86
+ return this.isRunning;
87
+ }
88
+ /** Set running state */
89
+ setRunning(running) {
90
+ this.isRunning = running;
91
+ }
92
+ /** Handle a JSON-RPC request */
93
+ async handleRequest(request) {
94
+ const { method, params, id } = request;
95
+ const handler = this.requestHandlers.get(method);
96
+ if (!handler) {
97
+ return {
98
+ jsonrpc: "2.0",
99
+ id,
100
+ error: {
101
+ code: -32601,
102
+ // Method not found
103
+ message: `Method not found: ${method}`
104
+ }
105
+ };
106
+ }
107
+ try {
108
+ const result = await handler(params);
109
+ return {
110
+ jsonrpc: "2.0",
111
+ id,
112
+ result
113
+ };
114
+ } catch (err) {
115
+ const errorMessage = err instanceof Error ? err.message : String(err);
116
+ return {
117
+ jsonrpc: "2.0",
118
+ id,
119
+ error: {
120
+ code: -32e3,
121
+ message: errorMessage
122
+ }
123
+ };
124
+ }
125
+ }
126
+ /** Handle a batch of JSON-RPC requests */
127
+ async handleBatch(requests) {
128
+ return Promise.all(requests.map((req) => this.handleRequest(req)));
129
+ }
130
+ /** Cleanup */
131
+ destroy() {
132
+ this.tools.clear();
133
+ this.requestHandlers.clear();
134
+ this.isRunning = false;
135
+ }
136
+ }
137
+ export function useMCPServer(options) {
138
+ const config = {
139
+ name: options.name,
140
+ version: options.version,
141
+ capabilities: {
142
+ tools: true
143
+ }
144
+ };
145
+ const tools = ref(options.tools || []);
146
+ const isRunning = ref(false);
147
+ const server = new MCPServer({
148
+ config,
149
+ tools: options.tools,
150
+ onStart: () => {
151
+ isRunning.value = true;
152
+ },
153
+ onStop: () => {
154
+ isRunning.value = false;
155
+ }
156
+ });
157
+ const addTool = (tool) => {
158
+ server.addTool(tool);
159
+ tools.value = server.getTools();
160
+ options.onToolsChange?.(tools.value);
161
+ };
162
+ const removeTool = (name) => {
163
+ const result = server.removeTool(name);
164
+ tools.value = server.getTools();
165
+ options.onToolsChange?.(tools.value);
166
+ return result;
167
+ };
168
+ const handleRequest = async (request) => {
169
+ return server.handleRequest(request);
170
+ };
171
+ const start = () => {
172
+ server.setRunning(true);
173
+ isRunning.value = true;
174
+ };
175
+ const stop = () => {
176
+ server.setRunning(false);
177
+ isRunning.value = false;
178
+ server.destroy();
179
+ };
180
+ onUnmounted(() => {
181
+ stop();
182
+ });
183
+ return {
184
+ server,
185
+ config,
186
+ tools,
187
+ isRunning,
188
+ addTool,
189
+ removeTool,
190
+ handleRequest,
191
+ start,
192
+ stop
193
+ };
194
+ }
195
+ export function createMCPServerHTTPHandler(options) {
196
+ const { server, handleRequest } = useMCPServer(options);
197
+ return async function(request) {
198
+ if (request.method === "OPTIONS") {
199
+ return new Response(null, {
200
+ headers: {
201
+ "Access-Control-Allow-Origin": "*",
202
+ "Access-Control-Allow-Methods": "GET, POST",
203
+ "Access-Control-Allow-Headers": "Content-Type"
204
+ }
205
+ });
206
+ }
207
+ try {
208
+ const body = await request.json();
209
+ if (Array.isArray(body)) {
210
+ const responses = await server.handleBatch(body);
211
+ return new Response(JSON.stringify(responses), {
212
+ headers: { "Content-Type": "application/json" }
213
+ });
214
+ }
215
+ const response = await handleRequest(body);
216
+ return new Response(JSON.stringify(response), {
217
+ headers: { "Content-Type": "application/json" }
218
+ });
219
+ } catch (err) {
220
+ const errorMessage = err instanceof Error ? err.message : String(err);
221
+ return new Response(
222
+ JSON.stringify({
223
+ jsonrpc: "2.0",
224
+ id: null,
225
+ error: {
226
+ code: -32603,
227
+ message: errorMessage
228
+ }
229
+ }),
230
+ { status: 500, headers: { "Content-Type": "application/json" } }
231
+ );
232
+ }
233
+ };
234
+ }
package/dist/mcp.cjs ADDED
@@ -0,0 +1,370 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useMCPClient = useMCPClient;
7
+ exports.useMCPTools = useMCPTools;
8
+ var _vue = require("vue");
9
+ let requestId = 0;
10
+ function createJSONRPCRequest(method, params) {
11
+ return {
12
+ jsonrpc: "2.0",
13
+ id: ++requestId,
14
+ method,
15
+ params
16
+ };
17
+ }
18
+ class MCPHTTPTransport {
19
+ serverUrl;
20
+ timeout;
21
+ constructor(serverUrl, timeout = 3e4) {
22
+ this.serverUrl = serverUrl;
23
+ this.timeout = timeout;
24
+ }
25
+ async sendRequest(method, params) {
26
+ const response = await fetch(this.serverUrl, {
27
+ method: "POST",
28
+ headers: {
29
+ "Content-Type": "application/json"
30
+ },
31
+ body: JSON.stringify(createJSONRPCRequest(method, params)),
32
+ signal: AbortSignal.timeout(this.timeout)
33
+ });
34
+ if (!response.ok) {
35
+ throw new Error(`MCP HTTP Error: ${response.status} ${response.statusText}`);
36
+ }
37
+ const data = await response.json();
38
+ if (data.error) {
39
+ throw new Error(`MCP Error: ${data.error.message}`);
40
+ }
41
+ return data.result;
42
+ }
43
+ }
44
+ class MCPStdioTransport {
45
+ command;
46
+ args;
47
+ env;
48
+ process = null;
49
+ requestMap = /* @__PURE__ */new Map();
50
+ notificationHandlers = /* @__PURE__ */new Map();
51
+ messageBuffer = "";
52
+ initialized;
53
+ constructor(command, args = [], env = {}) {
54
+ this.command = command;
55
+ this.args = args;
56
+ this.env = env;
57
+ this.initialized = this.init();
58
+ }
59
+ init() {
60
+ return new Promise((resolve, reject) => {
61
+ try {
62
+ const {
63
+ spawn
64
+ } = require("child_process");
65
+ this.process = spawn(this.command, this.args, {
66
+ env: {
67
+ ...process.env,
68
+ ...this.env
69
+ },
70
+ stdio: ["pipe", "pipe", "pipe"]
71
+ });
72
+ this.process.on("error", err => {
73
+ reject(err);
74
+ });
75
+ this.process.on("close", () => {
76
+ this.process = null;
77
+ });
78
+ this.process.stdout?.on("data", data => {
79
+ this.handleData(data.toString());
80
+ });
81
+ this.process.stderr?.on("data", data => {
82
+ console.error("[MCP Stdio]", data.toString());
83
+ });
84
+ resolve();
85
+ } catch (err) {
86
+ reject(err);
87
+ }
88
+ });
89
+ }
90
+ handleData(data) {
91
+ this.messageBuffer += data;
92
+ let newlineIndex;
93
+ while ((newlineIndex = this.messageBuffer.indexOf("\n")) !== -1) {
94
+ const line = this.messageBuffer.slice(0, newlineIndex);
95
+ this.messageBuffer = this.messageBuffer.slice(newlineIndex + 1);
96
+ if (!line.trim()) continue;
97
+ try {
98
+ const message = JSON.parse(line);
99
+ this.handleMessage(message);
100
+ } catch {}
101
+ }
102
+ }
103
+ handleMessage(message) {
104
+ if ("id" in message && message.id !== void 0) {
105
+ const pending = this.requestMap.get(message.id);
106
+ if (pending) {
107
+ this.requestMap.delete(message.id);
108
+ if ("error" in message && message.error) {
109
+ pending.reject(new Error(message.error.message));
110
+ } else {
111
+ pending.resolve(message.result);
112
+ }
113
+ }
114
+ } else if ("method" in message) {
115
+ const handler = this.notificationHandlers.get(message.method);
116
+ if (handler) {
117
+ handler(message.params);
118
+ }
119
+ }
120
+ }
121
+ async sendRequest(method, params) {
122
+ await this.initialized;
123
+ if (!this.process) {
124
+ throw new Error("MCP process not running");
125
+ }
126
+ const request = createJSONRPCRequest(method, params);
127
+ const requestId2 = request.id;
128
+ return new Promise((resolve, reject) => {
129
+ this.requestMap.set(requestId2, {
130
+ resolve,
131
+ reject
132
+ });
133
+ this.process.stdin?.write(JSON.stringify(request) + "\n");
134
+ setTimeout(() => {
135
+ if (this.requestMap.has(requestId2)) {
136
+ this.requestMap.delete(requestId2);
137
+ reject(new Error(`MCP request timeout: ${method}`));
138
+ }
139
+ }, 3e4);
140
+ });
141
+ }
142
+ onNotification(method, handler) {
143
+ this.notificationHandlers.set(method, handler);
144
+ }
145
+ async close() {
146
+ if (this.process) {
147
+ this.process.kill();
148
+ this.process = null;
149
+ }
150
+ }
151
+ }
152
+ function useMCPClient(options) {
153
+ const {
154
+ config,
155
+ autoConnect = true,
156
+ clientName = "@yh-ui/ai-sdk",
157
+ clientVersion = "1.0.0",
158
+ onToolsUpdate,
159
+ onConnectionChange
160
+ } = options;
161
+ const state = (0, _vue.shallowRef)({
162
+ isConnected: false,
163
+ isConnecting: false,
164
+ error: null,
165
+ tools: []
166
+ });
167
+ const isCallingTool = (0, _vue.ref)(false);
168
+ const transport = (0, _vue.shallowRef)(null);
169
+ const connect = async () => {
170
+ if (state.value.isConnected || state.value.isConnecting) {
171
+ return;
172
+ }
173
+ state.value = {
174
+ ...state.value,
175
+ isConnecting: true,
176
+ error: null
177
+ };
178
+ try {
179
+ let t;
180
+ if (config.serverUrl) {
181
+ t = new MCPHTTPTransport(config.serverUrl, config.timeout);
182
+ } else if (config.command) {
183
+ t = new MCPStdioTransport(config.command, config.args, config.env);
184
+ } else {
185
+ throw new Error("Invalid MCP config: must provide either serverUrl or command");
186
+ }
187
+ transport.value = t;
188
+ const _initResult = await t.sendRequest("initialize", {
189
+ protocolVersion: "2024-11-05",
190
+ capabilities: {},
191
+ clientInfo: {
192
+ name: clientName,
193
+ version: clientVersion
194
+ }
195
+ });
196
+ await t.sendRequest("initialized", {});
197
+ const toolsResult = await t.sendRequest("tools/list", {});
198
+ const tools = toolsResult.tools.map(tool => ({
199
+ name: tool.name,
200
+ description: tool.description || "",
201
+ inputSchema: tool.inputSchema
202
+ }));
203
+ state.value = {
204
+ isConnected: true,
205
+ isConnecting: false,
206
+ error: null,
207
+ tools
208
+ };
209
+ onToolsUpdate?.(tools);
210
+ onConnectionChange?.(true);
211
+ } catch (err) {
212
+ const error = err instanceof Error ? err : new Error(String(err));
213
+ state.value = {
214
+ isConnected: false,
215
+ isConnecting: false,
216
+ error,
217
+ tools: []
218
+ };
219
+ onConnectionChange?.(false);
220
+ throw error;
221
+ }
222
+ };
223
+ const disconnect = async () => {
224
+ const t = transport.value;
225
+ if (t) {
226
+ if ("close" in t) {
227
+ await t.close();
228
+ }
229
+ transport.value = null;
230
+ }
231
+ state.value = {
232
+ isConnected: false,
233
+ isConnecting: false,
234
+ error: null,
235
+ tools: []
236
+ };
237
+ onConnectionChange?.(false);
238
+ };
239
+ const callTool = async (name, args = {}) => {
240
+ if (!state.value.isConnected) {
241
+ throw new Error("MCP client not connected");
242
+ }
243
+ const t = transport.value;
244
+ if (!t) {
245
+ throw new Error("MCP transport not initialized");
246
+ }
247
+ isCallingTool.value = true;
248
+ try {
249
+ const result = await t.sendRequest("tools/call", {
250
+ name,
251
+ arguments: args
252
+ });
253
+ const textContent = result.content.find(c => c.type === "text");
254
+ if (result.isError) {
255
+ throw new Error(textContent?.text || "Tool execution failed");
256
+ }
257
+ if (textContent?.text) {
258
+ try {
259
+ return JSON.parse(textContent.text);
260
+ } catch {
261
+ return textContent.text;
262
+ }
263
+ }
264
+ return result;
265
+ } finally {
266
+ isCallingTool.value = false;
267
+ }
268
+ };
269
+ if (autoConnect) {
270
+ connect();
271
+ }
272
+ (0, _vue.onUnmounted)(() => {
273
+ disconnect();
274
+ });
275
+ return {
276
+ state,
277
+ connect,
278
+ disconnect,
279
+ callTool,
280
+ isCallingTool
281
+ };
282
+ }
283
+ function useMCPTools(options) {
284
+ const {
285
+ servers,
286
+ autoConnect = true,
287
+ onServerConnect
288
+ } = options;
289
+ const allTools = (0, _vue.ref)([]);
290
+ const serverStates = (0, _vue.ref)(servers.map((config, index) => ({
291
+ index,
292
+ config,
293
+ isConnected: false,
294
+ tools: [],
295
+ error: null
296
+ })));
297
+ const clients = [];
298
+ for (let i = 0; i < servers.length; i++) {
299
+ const serverState = serverStates.value[i];
300
+ const client = useMCPClient({
301
+ config: servers[i],
302
+ autoConnect: false,
303
+ onToolsUpdate: tools => {
304
+ serverState.tools = tools;
305
+ updateAllTools();
306
+ },
307
+ onConnectionChange: isConnected => {
308
+ serverState.isConnected = isConnected;
309
+ }
310
+ });
311
+ clients.push({
312
+ get state() {
313
+ return client.state.value;
314
+ },
315
+ connect: client.connect,
316
+ disconnect: client.disconnect
317
+ });
318
+ }
319
+ function updateAllTools() {
320
+ const tools = [];
321
+ for (const server of serverStates.value) {
322
+ if (server.isConnected) {
323
+ tools.push(...server.tools);
324
+ }
325
+ }
326
+ allTools.value = tools;
327
+ }
328
+ const connectAll = async () => {
329
+ for (let i = 0; i < clients.length; i++) {
330
+ const client = clients[i];
331
+ try {
332
+ await client.connect();
333
+ const tools = serverStates.value[i].tools;
334
+ onServerConnect?.(i, tools);
335
+ } catch (err) {
336
+ serverStates.value[i].error = err instanceof Error ? err : new Error(String(err));
337
+ }
338
+ }
339
+ };
340
+ const disconnectAll = async () => {
341
+ for (const client of clients) {
342
+ await client.disconnect();
343
+ }
344
+ };
345
+ const callTool = async (name, args = {}) => {
346
+ for (const server of serverStates.value) {
347
+ const tool = server.tools.find(t => t.name === name);
348
+ if (tool) {
349
+ const clientIndex = serverStates.value.indexOf(server);
350
+ const _client = clients[clientIndex];
351
+ const mcpClient = useMCPClient({
352
+ config: server.config,
353
+ autoConnect: false
354
+ });
355
+ return mcpClient.callTool(name, args);
356
+ }
357
+ }
358
+ throw new Error(`Tool not found: ${name}`);
359
+ };
360
+ if (autoConnect) {
361
+ connectAll();
362
+ }
363
+ return {
364
+ allTools,
365
+ serverStates,
366
+ connectAll,
367
+ disconnectAll,
368
+ callTool
369
+ };
370
+ }