@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.
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,206 @@
1
+ export interface MCPTool {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: Record<string, unknown>;
5
+ }
6
+ export interface MCPConnectionConfig {
7
+ /** HTTP Server URL */
8
+ serverUrl?: string;
9
+ /** Stdio Command */
10
+ command?: string;
11
+ /** Stdio Arguments */
12
+ args?: string[];
13
+ /** Stdio Environment */
14
+ env?: Record<string, string>;
15
+ /** Timeout for requests */
16
+ timeout?: number;
17
+ }
18
+ export interface MCPClientState {
19
+ isConnected: boolean;
20
+ isConnecting: boolean;
21
+ error: Error | null;
22
+ tools: MCPTool[];
23
+ }
24
+ export interface MCPInitializeParams {
25
+ protocolVersion: string;
26
+ capabilities: Record<string, unknown>;
27
+ clientInfo: {
28
+ name: string;
29
+ version: string;
30
+ };
31
+ }
32
+ export interface MCPToolsListParams {
33
+ }
34
+ export interface MCPToolsCallParams {
35
+ name: string;
36
+ arguments: Record<string, unknown>;
37
+ }
38
+ export interface UseMCPClientOptions {
39
+ /** MCP Server configuration */
40
+ config: MCPConnectionConfig;
41
+ /** Auto-connect on mount */
42
+ autoConnect?: boolean;
43
+ /** Client name for initialization */
44
+ clientName?: string;
45
+ /** Client version for initialization */
46
+ clientVersion?: string;
47
+ /** Callback when tools are updated */
48
+ onToolsUpdate?: (tools: MCPTool[]) => void;
49
+ /** Callback when connection state changes */
50
+ onConnectionChange?: (isConnected: boolean) => void;
51
+ }
52
+ export interface UseMCPClientReturn {
53
+ /** Connection state */
54
+ state: MCPClientState;
55
+ /** Connect to MCP server */
56
+ connect: () => Promise<void>;
57
+ /** Disconnect from MCP server */
58
+ disconnect: () => Promise<void>;
59
+ /** Call a tool */
60
+ callTool: <T = unknown>(name: string, args?: Record<string, unknown>) => Promise<T>;
61
+ /** Tool call loading state */
62
+ isCallingTool: {
63
+ value: boolean;
64
+ };
65
+ }
66
+ /**
67
+ * useMCPClient - MCP Client Hook
68
+ *
69
+ * 连接外部 MCP Server 并调用其工具
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const { state, connect, disconnect, callTool } = useMCPClient({
74
+ * config: {
75
+ * serverUrl: 'http://localhost:3000/mcp'
76
+ * }
77
+ * })
78
+ * ```
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * // Using stdio transport
83
+ * const { state, connect, callTool } = useMCPClient({
84
+ * config: {
85
+ * command: 'npx',
86
+ * args: ['-y', '@modelcontextprotocol/server-filesystem', './data']
87
+ * }
88
+ * })
89
+ * ```
90
+ */
91
+ export declare function useMCPClient(options: UseMCPClientOptions): {
92
+ state: import("vue").ShallowRef<MCPClientState, MCPClientState>;
93
+ connect: () => Promise<void>;
94
+ disconnect: () => Promise<void>;
95
+ callTool: <T = unknown>(name: string, args?: Record<string, unknown>) => Promise<T>;
96
+ isCallingTool: import("vue").Ref<boolean, boolean>;
97
+ };
98
+ export interface UseMCPToolsOptions {
99
+ /** MCP Server configurations (支持多个 server) */
100
+ servers: MCPConnectionConfig[];
101
+ /** Auto-connect on mount */
102
+ autoConnect?: boolean;
103
+ /** Convert MCP tools to AI SDK format */
104
+ convertToAITools?: boolean;
105
+ /** Callback when any server connects */
106
+ onServerConnect?: (serverIndex: number, tools: MCPTool[]) => void;
107
+ }
108
+ export interface MCPServerState {
109
+ index: number;
110
+ config: MCPConnectionConfig;
111
+ isConnected: boolean;
112
+ tools: MCPTool[];
113
+ error: Error | null;
114
+ }
115
+ export interface UseMCPToolsReturn {
116
+ /** All available tools from all servers */
117
+ allTools: {
118
+ value: MCPTool[];
119
+ };
120
+ /** Individual server states */
121
+ serverStates: {
122
+ value: MCPServerState[];
123
+ };
124
+ /** Connect to all servers */
125
+ connectAll: () => Promise<void>;
126
+ /** Disconnect from all servers */
127
+ disconnectAll: () => Promise<void>;
128
+ /** Call tool by name (resolves to correct server) */
129
+ callTool: <T = unknown>(name: string, args?: Record<string, unknown>) => Promise<T>;
130
+ }
131
+ /**
132
+ * useMCPTools - 管理多个 MCP Server 的工具
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * const { allTools, serverStates, callTool } = useMCPTools({
137
+ * servers: [
138
+ * { serverUrl: 'http://localhost:3001/mcp' },
139
+ * { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', './data'] }
140
+ * ]
141
+ * })
142
+ * ```
143
+ */
144
+ export declare function useMCPTools(options: UseMCPToolsOptions): {
145
+ allTools: import("vue").Ref<{
146
+ name: string;
147
+ description: string;
148
+ inputSchema: Record<string, unknown>;
149
+ }[], MCPTool[] | {
150
+ name: string;
151
+ description: string;
152
+ inputSchema: Record<string, unknown>;
153
+ }[]>;
154
+ serverStates: import("vue").Ref<{
155
+ index: number;
156
+ config: {
157
+ serverUrl?: string
158
+ /** Stdio Command */
159
+ | undefined;
160
+ command?: string
161
+ /** Stdio Arguments */
162
+ | undefined;
163
+ args?: string[]
164
+ /** Stdio Environment */
165
+ | undefined;
166
+ env?: Record<string, string>
167
+ /** Timeout for requests */
168
+ | undefined;
169
+ timeout?: number | undefined;
170
+ };
171
+ isConnected: boolean;
172
+ tools: {
173
+ name: string;
174
+ description: string;
175
+ inputSchema: Record<string, unknown>;
176
+ }[];
177
+ error: Error | null;
178
+ }[], MCPServerState[] | {
179
+ index: number;
180
+ config: {
181
+ serverUrl?: string
182
+ /** Stdio Command */
183
+ | undefined;
184
+ command?: string
185
+ /** Stdio Arguments */
186
+ | undefined;
187
+ args?: string[]
188
+ /** Stdio Environment */
189
+ | undefined;
190
+ env?: Record<string, string>
191
+ /** Timeout for requests */
192
+ | undefined;
193
+ timeout?: number | undefined;
194
+ };
195
+ isConnected: boolean;
196
+ tools: {
197
+ name: string;
198
+ description: string;
199
+ inputSchema: Record<string, unknown>;
200
+ }[];
201
+ error: Error | null;
202
+ }[]>;
203
+ connectAll: () => Promise<void>;
204
+ disconnectAll: () => Promise<void>;
205
+ callTool: <T = unknown>(name: string, args?: Record<string, unknown>) => Promise<T>;
206
+ };
package/dist/mcp.mjs ADDED
@@ -0,0 +1,354 @@
1
+ import { ref, shallowRef, onUnmounted } from "vue";
2
+ let requestId = 0;
3
+ function createJSONRPCRequest(method, params) {
4
+ return {
5
+ jsonrpc: "2.0",
6
+ id: ++requestId,
7
+ method,
8
+ params
9
+ };
10
+ }
11
+ class MCPHTTPTransport {
12
+ serverUrl;
13
+ timeout;
14
+ constructor(serverUrl, timeout = 3e4) {
15
+ this.serverUrl = serverUrl;
16
+ this.timeout = timeout;
17
+ }
18
+ async sendRequest(method, params) {
19
+ const response = await fetch(this.serverUrl, {
20
+ method: "POST",
21
+ headers: {
22
+ "Content-Type": "application/json"
23
+ },
24
+ body: JSON.stringify(createJSONRPCRequest(method, params)),
25
+ signal: AbortSignal.timeout(this.timeout)
26
+ });
27
+ if (!response.ok) {
28
+ throw new Error(`MCP HTTP Error: ${response.status} ${response.statusText}`);
29
+ }
30
+ const data = await response.json();
31
+ if (data.error) {
32
+ throw new Error(`MCP Error: ${data.error.message}`);
33
+ }
34
+ return data.result;
35
+ }
36
+ }
37
+ class MCPStdioTransport {
38
+ command;
39
+ args;
40
+ env;
41
+ process = null;
42
+ requestMap = /* @__PURE__ */ new Map();
43
+ notificationHandlers = /* @__PURE__ */ new Map();
44
+ messageBuffer = "";
45
+ initialized;
46
+ constructor(command, args = [], env = {}) {
47
+ this.command = command;
48
+ this.args = args;
49
+ this.env = env;
50
+ this.initialized = this.init();
51
+ }
52
+ init() {
53
+ return new Promise((resolve, reject) => {
54
+ try {
55
+ const { spawn } = require("child_process");
56
+ this.process = spawn(this.command, this.args, {
57
+ env: { ...process.env, ...this.env },
58
+ stdio: ["pipe", "pipe", "pipe"]
59
+ });
60
+ this.process.on("error", (err) => {
61
+ reject(err);
62
+ });
63
+ this.process.on("close", () => {
64
+ this.process = null;
65
+ });
66
+ this.process.stdout?.on("data", (data) => {
67
+ this.handleData(data.toString());
68
+ });
69
+ this.process.stderr?.on("data", (data) => {
70
+ console.error("[MCP Stdio]", data.toString());
71
+ });
72
+ resolve();
73
+ } catch (err) {
74
+ reject(err);
75
+ }
76
+ });
77
+ }
78
+ handleData(data) {
79
+ this.messageBuffer += data;
80
+ let newlineIndex;
81
+ while ((newlineIndex = this.messageBuffer.indexOf("\n")) !== -1) {
82
+ const line = this.messageBuffer.slice(0, newlineIndex);
83
+ this.messageBuffer = this.messageBuffer.slice(newlineIndex + 1);
84
+ if (!line.trim()) continue;
85
+ try {
86
+ const message = JSON.parse(line);
87
+ this.handleMessage(message);
88
+ } catch {
89
+ }
90
+ }
91
+ }
92
+ handleMessage(message) {
93
+ if ("id" in message && message.id !== void 0) {
94
+ const pending = this.requestMap.get(message.id);
95
+ if (pending) {
96
+ this.requestMap.delete(message.id);
97
+ if ("error" in message && message.error) {
98
+ pending.reject(new Error(message.error.message));
99
+ } else {
100
+ pending.resolve(message.result);
101
+ }
102
+ }
103
+ } else if ("method" in message) {
104
+ const handler = this.notificationHandlers.get(message.method);
105
+ if (handler) {
106
+ handler(message.params);
107
+ }
108
+ }
109
+ }
110
+ async sendRequest(method, params) {
111
+ await this.initialized;
112
+ if (!this.process) {
113
+ throw new Error("MCP process not running");
114
+ }
115
+ const request = createJSONRPCRequest(method, params);
116
+ const requestId2 = request.id;
117
+ return new Promise((resolve, reject) => {
118
+ this.requestMap.set(requestId2, {
119
+ resolve,
120
+ reject
121
+ });
122
+ this.process.stdin?.write(JSON.stringify(request) + "\n");
123
+ setTimeout(() => {
124
+ if (this.requestMap.has(requestId2)) {
125
+ this.requestMap.delete(requestId2);
126
+ reject(new Error(`MCP request timeout: ${method}`));
127
+ }
128
+ }, 3e4);
129
+ });
130
+ }
131
+ onNotification(method, handler) {
132
+ this.notificationHandlers.set(method, handler);
133
+ }
134
+ async close() {
135
+ if (this.process) {
136
+ this.process.kill();
137
+ this.process = null;
138
+ }
139
+ }
140
+ }
141
+ export function useMCPClient(options) {
142
+ const {
143
+ config,
144
+ autoConnect = true,
145
+ clientName = "@yh-ui/ai-sdk",
146
+ clientVersion = "1.0.0",
147
+ onToolsUpdate,
148
+ onConnectionChange
149
+ } = options;
150
+ const state = shallowRef({
151
+ isConnected: false,
152
+ isConnecting: false,
153
+ error: null,
154
+ tools: []
155
+ });
156
+ const isCallingTool = ref(false);
157
+ const transport = shallowRef(null);
158
+ const connect = async () => {
159
+ if (state.value.isConnected || state.value.isConnecting) {
160
+ return;
161
+ }
162
+ state.value = {
163
+ ...state.value,
164
+ isConnecting: true,
165
+ error: null
166
+ };
167
+ try {
168
+ let t;
169
+ if (config.serverUrl) {
170
+ t = new MCPHTTPTransport(config.serverUrl, config.timeout);
171
+ } else if (config.command) {
172
+ t = new MCPStdioTransport(config.command, config.args, config.env);
173
+ } else {
174
+ throw new Error("Invalid MCP config: must provide either serverUrl or command");
175
+ }
176
+ transport.value = t;
177
+ const _initResult = await t.sendRequest("initialize", {
178
+ protocolVersion: "2024-11-05",
179
+ capabilities: {},
180
+ clientInfo: {
181
+ name: clientName,
182
+ version: clientVersion
183
+ }
184
+ });
185
+ await t.sendRequest("initialized", {});
186
+ const toolsResult = await t.sendRequest("tools/list", {});
187
+ const tools = toolsResult.tools.map((tool) => ({
188
+ name: tool.name,
189
+ description: tool.description || "",
190
+ inputSchema: tool.inputSchema
191
+ }));
192
+ state.value = {
193
+ isConnected: true,
194
+ isConnecting: false,
195
+ error: null,
196
+ tools
197
+ };
198
+ onToolsUpdate?.(tools);
199
+ onConnectionChange?.(true);
200
+ } catch (err) {
201
+ const error = err instanceof Error ? err : new Error(String(err));
202
+ state.value = {
203
+ isConnected: false,
204
+ isConnecting: false,
205
+ error,
206
+ tools: []
207
+ };
208
+ onConnectionChange?.(false);
209
+ throw error;
210
+ }
211
+ };
212
+ const disconnect = async () => {
213
+ const t = transport.value;
214
+ if (t) {
215
+ if ("close" in t) {
216
+ await t.close();
217
+ }
218
+ transport.value = null;
219
+ }
220
+ state.value = {
221
+ isConnected: false,
222
+ isConnecting: false,
223
+ error: null,
224
+ tools: []
225
+ };
226
+ onConnectionChange?.(false);
227
+ };
228
+ const callTool = async (name, args = {}) => {
229
+ if (!state.value.isConnected) {
230
+ throw new Error("MCP client not connected");
231
+ }
232
+ const t = transport.value;
233
+ if (!t) {
234
+ throw new Error("MCP transport not initialized");
235
+ }
236
+ isCallingTool.value = true;
237
+ try {
238
+ const result = await t.sendRequest("tools/call", {
239
+ name,
240
+ arguments: args
241
+ });
242
+ const textContent = result.content.find((c) => c.type === "text");
243
+ if (result.isError) {
244
+ throw new Error(textContent?.text || "Tool execution failed");
245
+ }
246
+ if (textContent?.text) {
247
+ try {
248
+ return JSON.parse(textContent.text);
249
+ } catch {
250
+ return textContent.text;
251
+ }
252
+ }
253
+ return result;
254
+ } finally {
255
+ isCallingTool.value = false;
256
+ }
257
+ };
258
+ if (autoConnect) {
259
+ connect();
260
+ }
261
+ onUnmounted(() => {
262
+ disconnect();
263
+ });
264
+ return {
265
+ state,
266
+ connect,
267
+ disconnect,
268
+ callTool,
269
+ isCallingTool
270
+ };
271
+ }
272
+ export function useMCPTools(options) {
273
+ const { servers, autoConnect = true, onServerConnect } = options;
274
+ const allTools = ref([]);
275
+ const serverStates = ref(
276
+ servers.map((config, index) => ({
277
+ index,
278
+ config,
279
+ isConnected: false,
280
+ tools: [],
281
+ error: null
282
+ }))
283
+ );
284
+ const clients = [];
285
+ for (let i = 0; i < servers.length; i++) {
286
+ const serverState = serverStates.value[i];
287
+ const client = useMCPClient({
288
+ config: servers[i],
289
+ autoConnect: false,
290
+ onToolsUpdate: (tools) => {
291
+ serverState.tools = tools;
292
+ updateAllTools();
293
+ },
294
+ onConnectionChange: (isConnected) => {
295
+ serverState.isConnected = isConnected;
296
+ }
297
+ });
298
+ clients.push({
299
+ get state() {
300
+ return client.state.value;
301
+ },
302
+ connect: client.connect,
303
+ disconnect: client.disconnect
304
+ });
305
+ }
306
+ function updateAllTools() {
307
+ const tools = [];
308
+ for (const server of serverStates.value) {
309
+ if (server.isConnected) {
310
+ tools.push(...server.tools);
311
+ }
312
+ }
313
+ allTools.value = tools;
314
+ }
315
+ const connectAll = async () => {
316
+ for (let i = 0; i < clients.length; i++) {
317
+ const client = clients[i];
318
+ try {
319
+ await client.connect();
320
+ const tools = serverStates.value[i].tools;
321
+ onServerConnect?.(i, tools);
322
+ } catch (err) {
323
+ serverStates.value[i].error = err instanceof Error ? err : new Error(String(err));
324
+ }
325
+ }
326
+ };
327
+ const disconnectAll = async () => {
328
+ for (const client of clients) {
329
+ await client.disconnect();
330
+ }
331
+ };
332
+ const callTool = async (name, args = {}) => {
333
+ for (const server of serverStates.value) {
334
+ const tool = server.tools.find((t) => t.name === name);
335
+ if (tool) {
336
+ const clientIndex = serverStates.value.indexOf(server);
337
+ const _client = clients[clientIndex];
338
+ const mcpClient = useMCPClient({ config: server.config, autoConnect: false });
339
+ return mcpClient.callTool(name, args);
340
+ }
341
+ }
342
+ throw new Error(`Tool not found: ${name}`);
343
+ };
344
+ if (autoConnect) {
345
+ connectAll();
346
+ }
347
+ return {
348
+ allTools,
349
+ serverStates,
350
+ connectAll,
351
+ disconnectAll,
352
+ callTool
353
+ };
354
+ }
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createLangSmithExporter = createLangSmithExporter;
7
+ exports.createOTelConsoleExporter = createOTelConsoleExporter;
8
+ exports.createObservabilityManager = createObservabilityManager;
9
+ exports.toOTelSpan = toOTelSpan;
10
+ function createOTelConsoleExporter() {
11
+ return {
12
+ async exportSpans(spans) {
13
+ const payload = {
14
+ resourceSpans: [{
15
+ resource: {
16
+ attributes: [{
17
+ key: "service.name",
18
+ value: {
19
+ stringValue: "yh-ai-sdk"
20
+ }
21
+ }]
22
+ },
23
+ scopeSpans: [{
24
+ spans
25
+ }]
26
+ }]
27
+ };
28
+ console.log("[OTel Export]", JSON.stringify(payload, null, 2));
29
+ }
30
+ };
31
+ }
32
+ function createLangSmithExporter(config) {
33
+ const {
34
+ apiUrl = "https://api.langsmith.com/v1/runs",
35
+ apiKey,
36
+ projectName = "yh-ai-sdk"
37
+ } = config;
38
+ return {
39
+ async exportSpans(spans) {
40
+ if (!apiKey) {
41
+ console.warn("[LangSmith] No API key configured, skipping export");
42
+ return;
43
+ }
44
+ const runs = spans.map(span => {
45
+ const inputs = {};
46
+ const outputs = {};
47
+ let runType = "chain";
48
+ for (const attr of span.attributes) {
49
+ const key = attr.key;
50
+ const val = attr.value.stringValue ?? attr.value.intValue ?? attr.value.boolValue;
51
+ if (key.startsWith("input.")) {
52
+ inputs[key.slice(6)] = val;
53
+ } else if (key.startsWith("output.")) {
54
+ outputs[key.slice(7)] = val;
55
+ } else if (key === "run_type") {
56
+ runType = val;
57
+ }
58
+ }
59
+ return {
60
+ id: span.spanId,
61
+ name: span.name,
62
+ run_type: runType,
63
+ start_time: span.startTimeUnixNano / 1e6,
64
+ end_time: span.endTimeUnixNano ? span.endTimeUnixNano / 1e6 : Date.now(),
65
+ inputs,
66
+ outputs: Object.keys(outputs).length ? outputs : void 0,
67
+ error: span.status?.code === 2 ? span.status.message : void 0,
68
+ parent_run_id: span.parentSpanId,
69
+ metadata: {
70
+ project: projectName
71
+ }
72
+ };
73
+ });
74
+ try {
75
+ await fetch(apiUrl, {
76
+ method: "POST",
77
+ headers: {
78
+ "Content-Type": "application/json",
79
+ Authorization: `Bearer ${apiKey}`
80
+ },
81
+ body: JSON.stringify({
82
+ runs
83
+ })
84
+ });
85
+ } catch (err) {
86
+ console.error("[LangSmith] Export failed:", err);
87
+ }
88
+ }
89
+ };
90
+ }
91
+ function toOTelSpan(span, traceId) {
92
+ const startNs = span.startTime.getTime() * 1e6;
93
+ const endNs = span.endTime ? span.endTime.getTime() * 1e6 : Date.now() * 1e6;
94
+ const attrs = Object.entries(span.attributes).map(([key, value]) => ({
95
+ key,
96
+ value: typeof value === "string" ? {
97
+ stringValue: value
98
+ } : typeof value === "number" ? {
99
+ intValue: value
100
+ } : {
101
+ boolValue: Boolean(value)
102
+ }
103
+ }));
104
+ const events = span.events.map(e => ({
105
+ timeUnixNano: e.timestamp.getTime() * 1e6,
106
+ name: e.type,
107
+ attributes: Object.entries(e.data).map(([k, v]) => ({
108
+ key: k,
109
+ value: {
110
+ stringValue: String(v)
111
+ }
112
+ }))
113
+ }));
114
+ return {
115
+ traceId,
116
+ spanId: span.id,
117
+ name: span.name,
118
+ kind: "INTERNAL",
119
+ startTimeUnixNano: startNs,
120
+ endTimeUnixNano: endNs,
121
+ status: span.events.some(e => e.type === "error") ? {
122
+ code: 2,
123
+ message: "Error occurred"
124
+ } : {
125
+ code: 0
126
+ },
127
+ attributes: attrs,
128
+ events
129
+ };
130
+ }
131
+ function createObservabilityManager(config) {
132
+ const exporters = config?.exporters || [];
133
+ const traceId = config?.traceId || Math.random().toString(16).slice(2).padStart(32, "0");
134
+ return {
135
+ addExporter(exporter) {
136
+ exporters.push(exporter);
137
+ },
138
+ async flush() {
139
+ await Promise.all(exporters.map(async ex => {
140
+ if ("flush" in ex && typeof ex.flush === "function") {
141
+ await ex.flush();
142
+ }
143
+ }));
144
+ },
145
+ async export(tracerSpans) {
146
+ const otelSpans = tracerSpans.map(s => toOTelSpan(s, traceId));
147
+ await Promise.all(exporters.map(ex => ex.exportSpans(otelSpans)));
148
+ }
149
+ };
150
+ }