@opentiny/next-sdk 0.1.0 → 0.1.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.
@@ -0,0 +1,440 @@
1
+ import { MessageChannelServerTransport, createTransportPair } from '@opentiny/next'
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
3
+ import { z, ZodObject, ZodLiteral, ZodType, ZodOptional } from 'zod'
4
+ import {
5
+ SetLevelRequestSchema,
6
+ SubscribeRequestSchema,
7
+ UnsubscribeRequestSchema,
8
+ ListResourcesRequestSchema,
9
+ RootsListChangedNotificationSchema
10
+ } from '@modelcontextprotocol/sdk/types.js'
11
+ import type {
12
+ ToolCallback,
13
+ RegisteredTool,
14
+ PromptCallback,
15
+ RegisteredPrompt,
16
+ ResourceMetadata,
17
+ ResourceTemplate,
18
+ RegisteredResource,
19
+ ReadResourceCallback,
20
+ RegisteredResourceTemplate,
21
+ ReadResourceTemplateCallback
22
+ } from '@modelcontextprotocol/sdk/server/mcp.js'
23
+ import type {
24
+ Result,
25
+ Request,
26
+ Notification,
27
+ Implementation,
28
+ ToolAnnotations,
29
+ ServerCapabilities,
30
+ ClientCapabilities,
31
+ ElicitResult,
32
+ ElicitRequest,
33
+ ListRootsRequest,
34
+ CreateMessageRequest,
35
+ LoggingMessageNotification,
36
+ ResourceUpdatedNotification
37
+ } from '@modelcontextprotocol/sdk/types.js'
38
+ import type { ZodRawShape, ZodTypeDef } from 'zod'
39
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
40
+ import type { ServerOptions } from '@modelcontextprotocol/sdk/server/index.js'
41
+ import type {
42
+ RequestOptions,
43
+ NotificationOptions,
44
+ RequestHandlerExtra
45
+ } from '@modelcontextprotocol/sdk/shared/protocol.js'
46
+
47
+ type PromptArgsRawShape = {
48
+ [k: string]: ZodType<string, ZodTypeDef, string> | ZodOptional<ZodType<string, ZodTypeDef, string>>
49
+ }
50
+
51
+ type SendRequestT = Request
52
+ type SendNotificationT = Notification
53
+ type SendResultT = Result
54
+
55
+ /**
56
+ * High-level Web MCP server that provides a simpler API for working with resources, tools, and prompts.
57
+ * For advanced usage (like sending notifications or setting custom request handlers), use the underlying
58
+ * Server instance available via the `server` property.
59
+ */
60
+ export class WebMcpServer {
61
+ public readonly server: McpServer
62
+ public transport: Transport | undefined
63
+
64
+ constructor(serverInfo: Implementation, options?: ServerOptions) {
65
+ const info: Implementation = {
66
+ name: 'web-mcp-server',
67
+ version: '1.0.0'
68
+ }
69
+
70
+ const capabilities: ServerCapabilities = {
71
+ prompts: { listChanged: true },
72
+ resources: { subscribe: true, listChanged: true },
73
+ tools: { listChanged: true },
74
+ completions: {},
75
+ logging: {}
76
+ }
77
+
78
+ this.server = new McpServer(serverInfo || info, options || { capabilities })
79
+
80
+ this.server.server.oninitialized = () => {
81
+ this.oninitialized?.()
82
+ }
83
+
84
+ this.server.server.onclose = () => {
85
+ this.onclose?.()
86
+ }
87
+
88
+ this.server.server.onerror = (error: Error) => {
89
+ this.onerror?.(error)
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Connects the server to a transport via the specified option.
95
+ */
96
+ async connect(options: Transport | string): Promise<Transport> {
97
+ if (typeof (options as Transport)['start'] === 'function') {
98
+ this.transport = options as Transport
99
+ this.transport.onclose = undefined
100
+ this.transport.onerror = undefined
101
+ this.transport.onmessage = undefined
102
+ } else {
103
+ this.transport = new MessageChannelServerTransport(options as string)
104
+ await (this.transport as MessageChannelServerTransport).listen()
105
+ }
106
+
107
+ await this.server.connect(this.transport)
108
+ return this.transport
109
+ }
110
+
111
+ /**
112
+ * Callback for when initialization has fully completed (i.e., the client has sent an `initialized` notification).
113
+ */
114
+ oninitialized?: () => void
115
+
116
+ /**
117
+ * Callback for when the connection is closed for any reason.
118
+ *
119
+ * This is invoked when close() is called as well.
120
+ */
121
+ onclose?: () => void
122
+
123
+ /**
124
+ * Callback for when an error occurs.
125
+ *
126
+ * Note that errors are not necessarily fatal; they are used for reporting any kind of exceptional condition out of band.
127
+ */
128
+ onerror?: (error: Error) => void
129
+
130
+ /**
131
+ * Closes the connection.
132
+ */
133
+ async close(): Promise<void> {
134
+ await this.server.close()
135
+ }
136
+
137
+ /**
138
+ * Registers a tool with a config object and callback.
139
+ */
140
+ registerTool<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(
141
+ name: string,
142
+ config: {
143
+ title?: string
144
+ description?: string
145
+ inputSchema?: InputArgs
146
+ outputSchema?: OutputArgs
147
+ annotations?: ToolAnnotations
148
+ },
149
+ cb: ToolCallback<InputArgs>
150
+ ): RegisteredTool {
151
+ return this.server.registerTool(name, config, cb)
152
+ }
153
+
154
+ /**
155
+ * Registers a prompt with a config object and callback.
156
+ */
157
+ registerPrompt<Args extends PromptArgsRawShape>(
158
+ name: string,
159
+ config: {
160
+ title?: string
161
+ description?: string
162
+ argsSchema?: Args
163
+ },
164
+ cb: PromptCallback<Args>
165
+ ): RegisteredPrompt {
166
+ return this.server.registerPrompt(name, config, cb)
167
+ }
168
+
169
+ /**
170
+ * Registers a resource with a config object and callback.
171
+ * For static resources, use a URI string. For dynamic resources, use a ResourceTemplate.
172
+ */
173
+ registerResource(
174
+ name: string,
175
+ uriOrTemplate: string,
176
+ config: ResourceMetadata,
177
+ readCallback: ReadResourceCallback
178
+ ): RegisteredResource
179
+ registerResource(
180
+ name: string,
181
+ uriOrTemplate: ResourceTemplate,
182
+ config: ResourceMetadata,
183
+ readCallback: ReadResourceTemplateCallback
184
+ ): RegisteredResourceTemplate
185
+ registerResource(
186
+ name: string,
187
+ uriOrTemplate: string | ResourceTemplate,
188
+ config: ResourceMetadata,
189
+ readCallback: ReadResourceCallback | ReadResourceTemplateCallback
190
+ ): RegisteredResource | RegisteredResourceTemplate {
191
+ if (typeof uriOrTemplate === 'string') {
192
+ return this.server.registerResource(name, uriOrTemplate, config, readCallback as ReadResourceCallback)
193
+ } else {
194
+ return this.server.registerResource(name, uriOrTemplate, config, readCallback as ReadResourceTemplateCallback)
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Checks if the server is connected to a transport.
200
+ * @returns True if the server is connected
201
+ */
202
+ isConnected() {
203
+ return this.server.isConnected()
204
+ }
205
+
206
+ /**
207
+ * Sends a resource list changed event to the client, if connected.
208
+ */
209
+ sendResourceListChanged() {
210
+ this.server.sendResourceListChanged()
211
+ }
212
+
213
+ /**
214
+ * Sends a tool list changed event to the client, if connected.
215
+ */
216
+ sendToolListChanged() {
217
+ this.server.sendToolListChanged()
218
+ }
219
+
220
+ /**
221
+ * Sends a prompt list changed event to the client, if connected.
222
+ */
223
+ sendPromptListChanged() {
224
+ this.server.sendPromptListChanged()
225
+ }
226
+
227
+ /**
228
+ * After initialization has completed, this will be populated with the client's reported capabilities.
229
+ */
230
+ getClientCapabilities(): ClientCapabilities | undefined {
231
+ return this.server.server.getClientCapabilities()
232
+ }
233
+
234
+ /**
235
+ * After initialization has completed, this will be populated with information about the client's name and version.
236
+ */
237
+ getClientVersion(): Implementation | undefined {
238
+ return this.server.server.getClientVersion()
239
+ }
240
+
241
+ /**
242
+ * Sends a ping to the client to check if it is still connected.
243
+ */
244
+ async ping() {
245
+ return await this.server.server.ping()
246
+ }
247
+
248
+ /**
249
+ * Creates a LLM message to be sent to the client.
250
+ */
251
+ async createMessage(params: CreateMessageRequest['params'], options?: RequestOptions) {
252
+ return await this.server.server.createMessage(params, options)
253
+ }
254
+
255
+ /**
256
+ * Elicits input from the client, such as a prompt or resource.
257
+ */
258
+ async elicitInput(params: ElicitRequest['params'], options?: RequestOptions): Promise<ElicitResult> {
259
+ return await this.server.server.elicitInput(params, options)
260
+ }
261
+
262
+ /**
263
+ * Lists the root resources available to the client.
264
+ */
265
+ async listRoots(params?: ListRootsRequest['params'], options?: RequestOptions) {
266
+ return await this.server.server.listRoots(params, options)
267
+ }
268
+
269
+ /**
270
+ * Sends a logging message to the client.
271
+ */
272
+ async sendLoggingMessage(params: LoggingMessageNotification['params']) {
273
+ return await this.server.server.sendLoggingMessage(params)
274
+ }
275
+
276
+ /**
277
+ * Sends a resource updated notification to the client.
278
+ */
279
+ async sendResourceUpdated(params: ResourceUpdatedNotification['params']) {
280
+ return await this.server.server.sendResourceUpdated(params)
281
+ }
282
+
283
+ /**
284
+ * Sends a request and wait for a response.
285
+ *
286
+ * Do not use this method to emit notifications! Use notification() instead.
287
+ */
288
+ request<T extends ZodType<object>>(
289
+ request: SendRequestT,
290
+ resultSchema: T,
291
+ options?: RequestOptions
292
+ ): Promise<z.infer<T>> {
293
+ return this.server.server.request(request, resultSchema, options)
294
+ }
295
+
296
+ /**
297
+ * Emits a notification, which is a one-way message that does not expect a response.
298
+ */
299
+ async notification(notification: SendNotificationT, options?: NotificationOptions): Promise<void> {
300
+ return await this.server.server.notification(notification, options)
301
+ }
302
+
303
+ /**
304
+ * Registers a handler to invoke when this protocol object receives a request with the given method.
305
+ *
306
+ * Note that this will replace any previous request handler for the same method.
307
+ */
308
+ setRequestHandler<
309
+ T extends ZodObject<{
310
+ method: ZodLiteral<string>
311
+ }>
312
+ >(
313
+ requestSchema: T,
314
+ handler: (
315
+ request: z.infer<T>,
316
+ extra: RequestHandlerExtra<SendRequestT, SendNotificationT>
317
+ ) => SendResultT | Promise<SendResultT>
318
+ ): void {
319
+ this.server.server.setRequestHandler(requestSchema, handler)
320
+ }
321
+
322
+ /**
323
+ * Removes the request handler for the given method.
324
+ */
325
+ removeRequestHandler(method: string): void {
326
+ this.server.server.removeRequestHandler(method)
327
+ }
328
+
329
+ /**
330
+ * Registers a handler to invoke when this protocol object receives a notification with the given method.
331
+ *
332
+ * Note that this will replace any previous notification handler for the same method.
333
+ */
334
+ setNotificationHandler<
335
+ T extends ZodObject<{
336
+ method: ZodLiteral<string>
337
+ }>
338
+ >(notificationSchema: T, handler: (notification: z.infer<T>) => void | Promise<void>): void {
339
+ this.server.server.setNotificationHandler(notificationSchema, handler)
340
+ }
341
+
342
+ /**
343
+ * Removes the notification handler for the given method.
344
+ */
345
+ removeNotificationHandler(method: string): void {
346
+ this.server.server.removeNotificationHandler(method)
347
+ }
348
+
349
+ /**
350
+ * Registers a handler for the subscribe request.
351
+ */
352
+ onSubscribe(
353
+ handler: (
354
+ request: z.infer<typeof SubscribeRequestSchema>,
355
+ extra: RequestHandlerExtra<SendRequestT, SendNotificationT>
356
+ ) => SendResultT | Promise<SendResultT>
357
+ ): void {
358
+ this.server.server.setRequestHandler(SubscribeRequestSchema, handler)
359
+ }
360
+
361
+ /**
362
+ * Registers a handler for the unsubscribe request.
363
+ */
364
+ onUnsubscribe(
365
+ handler: (
366
+ request: z.infer<typeof UnsubscribeRequestSchema>,
367
+ extra: RequestHandlerExtra<SendRequestT, SendNotificationT>
368
+ ) => SendResultT | Promise<SendResultT>
369
+ ): void {
370
+ this.server.server.setRequestHandler(UnsubscribeRequestSchema, handler)
371
+ }
372
+
373
+ /**
374
+ * Registers a handler for the set log level request.
375
+ */
376
+ onSetLogLevel(
377
+ handler: (
378
+ request: z.infer<typeof SetLevelRequestSchema>,
379
+ extra: RequestHandlerExtra<SendRequestT, SendNotificationT>
380
+ ) => SendResultT | Promise<SendResultT>
381
+ ): void {
382
+ this.server.server.setRequestHandler(SetLevelRequestSchema, handler)
383
+ }
384
+
385
+ /**
386
+ * Registers a handler for the list tools request.
387
+ */
388
+ onListResources(
389
+ handler: (
390
+ request: z.infer<typeof ListResourcesRequestSchema>,
391
+ extra: RequestHandlerExtra<SendRequestT, SendNotificationT>
392
+ ) => SendResultT | Promise<SendResultT>
393
+ ): void {
394
+ this.server.server.setRequestHandler(ListResourcesRequestSchema, handler)
395
+ }
396
+
397
+ /**
398
+ * Registers a handler for the roots list changed notification.
399
+ */
400
+ onRootsListChanged(
401
+ handler: (notification: z.infer<typeof RootsListChangedNotificationSchema>) => void | Promise<void>
402
+ ): void {
403
+ this.server.server.setNotificationHandler(RootsListChangedNotificationSchema, handler)
404
+ }
405
+
406
+ /**
407
+ * Close the transport for window.addEventListener('pagehide')
408
+ */
409
+ async onPagehide(event: PageTransitionEvent) {
410
+ if (event.persisted) {
411
+ return
412
+ }
413
+
414
+ if (this.transport && typeof this.transport['close'] === 'function') {
415
+ await this.transport.close()
416
+ }
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Creates a new MessageChannelServerTransport instance.
422
+ */
423
+ export const createMessageChannelServerTransport = (endpoint: string, globalObject?: object) =>
424
+ new MessageChannelServerTransport(endpoint, globalObject)
425
+
426
+ /**
427
+ * Creates a pair of transports for communication between a server and client using MessageChannel.
428
+ */
429
+ export const createMessageChannelPairTransport = () => createTransportPair()
430
+
431
+ /**
432
+ * Checks if the transport is a MessageChannelServerTransport.
433
+ */
434
+ export const isMessageChannelServerTransport = (transport: unknown): transport is MessageChannelServerTransport =>
435
+ transport instanceof MessageChannelServerTransport
436
+
437
+ /**
438
+ * Checks if the server is an instance of MCP Server.
439
+ */
440
+ export const isMcpServer = (server: unknown): server is McpServer => server instanceof McpServer
@@ -0,0 +1,88 @@
1
+ import { streamText, stepCountIs, generateText } from 'ai'
2
+ import type { ToolSet } from 'ai'
3
+ import { getMcpClients, getMcpTools } from './utils'
4
+ import type { IAgentModelProviderOption, McpServerConfig } from './type'
5
+ import { AIProviderFactories } from './utils/aiProviderFactories'
6
+ import { ProviderV2 } from '@ai-sdk/provider'
7
+ import { OpenAIProvider } from '@ai-sdk/openai'
8
+
9
+ export class AgentModelProvider {
10
+ llm: ProviderV2 | OpenAIProvider
11
+ mcpServers: McpServerConfig[]
12
+ isGetMcpClients = false
13
+ mcpClients: any[] = []
14
+
15
+ constructor({ llmConfig, mcpServers, llm }: IAgentModelProviderOption) {
16
+ // 1、保存 mcpServer
17
+ this.mcpServers = mcpServers || []
18
+
19
+ // 2、保存 llm
20
+ if (llm) {
21
+ this.llm = llm
22
+ } else if (llmConfig) {
23
+ let providerFn: (options: any) => ProviderV2 | OpenAIProvider
24
+
25
+ if (typeof llmConfig.providerType === 'string') {
26
+ providerFn = AIProviderFactories[llmConfig.providerType]
27
+ } else {
28
+ providerFn = llmConfig.providerType
29
+ }
30
+ this.llm = providerFn({
31
+ apiKey: llmConfig.apiKey,
32
+ baseURL: llmConfig.baseURL
33
+ })
34
+ } else {
35
+ throw new Error('Either llmConfig or llm must be provided')
36
+ }
37
+ }
38
+
39
+ async initClients() {
40
+ if (!this.isGetMcpClients) {
41
+ this.mcpClients = await getMcpClients(this.mcpServers)
42
+ this.isGetMcpClients = true
43
+ }
44
+ }
45
+ async chat({
46
+ model,
47
+ maxSteps = 5,
48
+ ...options
49
+ }: Parameters<typeof generateText>[0] & { maxSteps?: number }): Promise<any> {
50
+ if (!this.llm) {
51
+ throw new Error('LLM is not initialized')
52
+ }
53
+
54
+ // 每次会话需要获取最新的工具列表,因为工具是会发生变化的
55
+ await this.initClients()
56
+ const tools = await getMcpTools(this.mcpClients)
57
+
58
+ return generateText({
59
+ // @ts-ignore ProviderV2 是所有llm的父类, 在每一个具体的llm 类都有一个选择model的函数用法
60
+ model: this.llm(model),
61
+ tools: tools as ToolSet,
62
+ stopWhen: stepCountIs(maxSteps),
63
+ ...options
64
+ })
65
+ }
66
+
67
+ async chatStream({
68
+ model,
69
+ maxSteps = 5,
70
+ ...options
71
+ }: Parameters<typeof streamText>[0] & { maxSteps?: number }): Promise<any> {
72
+ if (!this.llm) {
73
+ throw new Error('LLM is not initialized')
74
+ }
75
+
76
+ // 每次会话需要获取最新的工具列表,因为工具是会发生变化的
77
+ await this.initClients()
78
+ const tools = await getMcpTools(this.mcpClients)
79
+
80
+ return streamText({
81
+ // @ts-ignore 同上
82
+ model: this.llm(model),
83
+ tools: tools as ToolSet,
84
+ stopWhen: stepCountIs(maxSteps),
85
+ ...options
86
+ })
87
+ }
88
+ }
package/agent/type.ts ADDED
@@ -0,0 +1,30 @@
1
+ export type { experimental_MCPClient as MCPClient } from 'ai'
2
+ import type { ProviderV2 } from '@ai-sdk/provider'
3
+ import type { MCPTransport } from 'ai'
4
+
5
+ /** 代理模型提供器的大语言配置对象 */
6
+ export interface IAgentModelProviderLlmConfig {
7
+ apiKey: string
8
+ baseURL: string
9
+ /** 支持内置的常用模型,或者传入一个ai-sdk官方的Provider工厂函数
10
+ * @example
11
+ * import { createOpenAI } from '@ai-sdk/openai'
12
+ */
13
+ providerType: 'openai' | 'deepseek' | ((options: any) => ProviderV2)
14
+ }
15
+
16
+ /** Mcp Server的配置对象 */
17
+ export type McpServerConfig = { type: 'streamableHttp' | 'sse'; url: string } | { transport: MCPTransport }
18
+
19
+ /** */
20
+ export interface IAgentModelProviderOption {
21
+ /** ai-sdk官方的Provider实例,不能与 llmConfig 同时传入
22
+ * @example
23
+ * import { openai } from '@ai-sdk/openai'
24
+ */
25
+ llm?: ProviderV2
26
+ /** 代理模型提供器的大语言配置对象, 不能与 llm 同时传入 */
27
+ llmConfig?: IAgentModelProviderLlmConfig
28
+ /** Mcp Server的配置对象的集合 */
29
+ mcpServers?: McpServerConfig[]
30
+ }
@@ -0,0 +1,7 @@
1
+ import { createOpenAI } from '@ai-sdk/openai'
2
+ import { createDeepSeek } from '@ai-sdk/deepseek'
3
+
4
+ export const AIProviderFactories = {
5
+ ['openai']: createOpenAI,
6
+ ['deepseek']: createDeepSeek
7
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ experimental_createMCPClient as createMCPClient,
3
+ ToolSet,
4
+ experimental_MCPClientConfig as MCPClientConfig
5
+ } from 'ai'
6
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
7
+ import { McpServerConfig, MCPClient } from '../type'
8
+
9
+ /** 创建 McpClients, 其中 mcpServers 允许为配置为 McpServerConfig, 或者任意的 MCPTransport
10
+ * 参考: https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#initializing-an-mcp-client
11
+ */
12
+ export const getMcpClients = async (mcpServers: McpServerConfig[]) => {
13
+ if (!mcpServers || mcpServers?.length === 0) {
14
+ return []
15
+ }
16
+ // 使用 Promise.all 并行处理所有 mcpServer 项
17
+ const allMcpClients = await Promise.all(
18
+ mcpServers.map(async (item: McpServerConfig) => {
19
+ try {
20
+ let transport: MCPClientConfig['transport']
21
+ // FIXME
22
+ if ('type' in item && item.type === 'streamableHttp') {
23
+ transport = new StreamableHTTPClientTransport(new URL(item.url))
24
+ } else {
25
+ transport = item as MCPClientConfig['transport'] // sse 或 自定义的 MCPTranport
26
+ }
27
+
28
+ return createMCPClient({ transport: transport as MCPClientConfig['transport'] })
29
+ } catch (error) {
30
+ console.error(`Failed to create MCP client`, item, error)
31
+ return []
32
+ }
33
+ })
34
+ )
35
+
36
+ return allMcpClients
37
+ }
38
+
39
+ /** 合并所有的Mcp Tools */
40
+ export const getMcpTools = async (mcpClients: MCPClient[]): Promise<ToolSet> => {
41
+ const tools = await Promise.all(mcpClients.map((client) => client?.tools?.()))
42
+
43
+ return tools.reduce((acc, curr) => ({ ...acc, ...curr }), {})
44
+ }
@@ -0,0 +1,18 @@
1
+ import { streamText, generateText } from 'ai';
2
+ import type { IAgentModelProviderOption, McpServerConfig } from './type';
3
+ import { ProviderV2 } from '@ai-sdk/provider';
4
+ import { OpenAIProvider } from '@ai-sdk/openai';
5
+ export declare class AgentModelProvider {
6
+ llm: ProviderV2 | OpenAIProvider;
7
+ mcpServers: McpServerConfig[];
8
+ isGetMcpClients: boolean;
9
+ mcpClients: any[];
10
+ constructor({ llmConfig, mcpServers, llm }: IAgentModelProviderOption);
11
+ initClients(): Promise<void>;
12
+ chat({ model, maxSteps, ...options }: Parameters<typeof generateText>[0] & {
13
+ maxSteps?: number;
14
+ }): Promise<any>;
15
+ chatStream({ model, maxSteps, ...options }: Parameters<typeof streamText>[0] & {
16
+ maxSteps?: number;
17
+ }): Promise<any>;
18
+ }