@opentiny/next-sdk 0.1.9 → 0.1.11

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.
Files changed (58) hide show
  1. package/WebMcp.ts +2 -0
  2. package/WebMcpClient.ts +9 -37
  3. package/agent/AgentModelProvider.ts +87 -59
  4. package/agent/type.ts +7 -3
  5. package/agent/utils/getAISDKTools.ts +2 -2
  6. package/dist/WebMcpClient.d.ts +361 -28
  7. package/dist/WebMcpClient.js +9 -33
  8. package/dist/agent/AgentModelProvider.d.ts +24 -19
  9. package/dist/agent/AgentModelProvider.js +83 -53
  10. package/dist/agent/type.d.ts +10 -3
  11. package/dist/agent/utils/getAISDKTools.d.ts +2 -2
  12. package/dist/agent/utils/getAISDKTools.js +2 -2
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.es.dev.js +7164 -4997
  15. package/dist/index.es.js +17082 -15330
  16. package/dist/index.js +4 -0
  17. package/dist/index.umd.dev.js +6636 -4469
  18. package/dist/index.umd.js +56 -61
  19. package/dist/{mcpsdk@1.17.0.dev.js → mcpsdk@1.20.1.dev.js} +587 -451
  20. package/dist/{mcpsdk@1.17.0.es.dev.js → mcpsdk@1.20.1.es.dev.js} +587 -451
  21. package/dist/{mcpsdk@1.17.0.es.js → mcpsdk@1.20.1.es.js} +4140 -4045
  22. package/dist/mcpsdk@1.20.1.js +16 -0
  23. package/dist/transport/ExtensionClientTransport.d.ts +23 -0
  24. package/dist/transport/ExtensionClientTransport.js +81 -0
  25. package/dist/transport/ExtensionContentServerTransport.d.ts +38 -0
  26. package/dist/transport/ExtensionContentServerTransport.js +128 -0
  27. package/dist/transport/ExtensionPageServerTransport.d.ts +37 -0
  28. package/dist/transport/ExtensionPageServerTransport.js +118 -0
  29. package/dist/transport/messages.d.ts +9 -0
  30. package/dist/transport/messages.js +51 -0
  31. package/dist/utils/uuid.d.ts +1 -0
  32. package/dist/utils/uuid.js +10 -0
  33. package/dist/webagent.dev.js +4482 -3858
  34. package/dist/webagent.es.dev.js +4483 -3859
  35. package/dist/webagent.es.js +9727 -9141
  36. package/dist/webagent.js +49 -58
  37. package/dist/webmcp-full.dev.js +729 -482
  38. package/dist/webmcp-full.es.dev.js +729 -482
  39. package/dist/webmcp-full.es.js +5717 -5534
  40. package/dist/webmcp-full.js +13 -13
  41. package/dist/webmcp.dev.js +145 -31
  42. package/dist/webmcp.es.dev.js +145 -31
  43. package/dist/webmcp.es.js +658 -569
  44. package/dist/webmcp.js +1 -1
  45. package/index.ts +5 -0
  46. package/package.json +2 -2
  47. package/remoter/svgs/chat.svg +43 -0
  48. package/remoter/svgs/icon-copy.svg +25 -0
  49. package/remoter/svgs/link.svg +28 -0
  50. package/remoter/svgs/qrcode.svg +35 -0
  51. package/remoter/svgs/scan.svg +21 -0
  52. package/runtime.html +21 -80
  53. package/transport/ExtensionClientTransport.ts +90 -0
  54. package/transport/ExtensionContentServerTransport.ts +173 -0
  55. package/transport/ExtensionPageServerTransport.ts +161 -0
  56. package/transport/messages.ts +63 -0
  57. package/utils/uuid.ts +10 -0
  58. package/dist/mcpsdk@1.17.0.js +0 -16
package/WebMcp.ts CHANGED
@@ -16,3 +16,5 @@ export type * from '@modelcontextprotocol/sdk/shared/transport.js'
16
16
  export type * from '@modelcontextprotocol/sdk/client/sse.js'
17
17
  export type * from '@modelcontextprotocol/sdk/client/streamableHttp.js'
18
18
  export type * from '@modelcontextprotocol/sdk/server/mcp.js'
19
+
20
+ export * from './transport/ExtensionPageServerTransport'
package/WebMcpClient.ts CHANGED
@@ -18,11 +18,9 @@ import {
18
18
  MessageChannelClientTransport,
19
19
  sseOptions,
20
20
  streamOptions,
21
- attemptConnection,
22
21
  createSseProxy,
23
22
  createStreamProxy,
24
- createSocketProxy,
25
- AuthClientProvider
23
+ createSocketProxy
26
24
  } from '@opentiny/next'
27
25
  import type {
28
26
  Result,
@@ -61,12 +59,9 @@ export interface ClientConnectOptions {
61
59
  url: string
62
60
  token?: string
63
61
  sessionId?: string
64
- authProvider?: AuthClientProvider
65
62
  type?: 'channel' | 'sse' | 'stream' | 'socket'
66
63
  agent?: boolean
67
64
  onError?: (error: Error) => void
68
- onUnauthorized?: (connect: () => Promise<void>) => Promise<void>
69
- onReconnect?: () => Promise<void>
70
65
  }
71
66
 
72
67
  type SendRequestT = Request
@@ -117,13 +112,11 @@ export class WebMcpClient {
117
112
  return { transport: this.transport, sessionId: this.transport.sessionId as string }
118
113
  }
119
114
 
120
- const { url, token, sessionId, authProvider, type, agent, onError, onUnauthorized, onReconnect } =
121
- options as ClientConnectOptions
115
+ const { url, token, sessionId, type, agent, onError } = options as ClientConnectOptions
122
116
 
123
117
  if (agent === true) {
124
- const proxyOptions: ProxyOptions = { client: this.client, url, token, sessionId, authProvider }
118
+ const proxyOptions: ProxyOptions = { client: this.client, url, token, sessionId }
125
119
 
126
- let reconnect = false
127
120
  let response
128
121
 
129
122
  const connectProxy = async () => {
@@ -136,17 +129,6 @@ export class WebMcpClient {
136
129
 
137
130
  transport.onerror = async (error: Error) => {
138
131
  onError?.(error)
139
-
140
- if (error.message === 'Unauthorized' && !reconnect) {
141
- if (typeof onUnauthorized === 'function') {
142
- await onUnauthorized(connectProxy)
143
- } else {
144
- reconnect = true
145
- await connectProxy()
146
- reconnect = false
147
- await onReconnect?.()
148
- }
149
- }
150
132
  }
151
133
 
152
134
  response = { transport, sessionId }
@@ -165,14 +147,9 @@ export class WebMcpClient {
165
147
  }
166
148
 
167
149
  if (type === 'sse') {
168
- if (authProvider) {
169
- const createTransport = () => new SSEClientTransport(endpoint, { authProvider })
170
- transport = await attemptConnection(this.client, authProvider.waitForOAuthCode, createTransport)
171
- } else {
172
- const opts = sseOptions(token, sessionId) as SSEClientTransportOptions
173
- transport = new SSEClientTransport(endpoint, opts)
174
- await this.client.connect(transport)
175
- }
150
+ const opts = sseOptions(token, sessionId) as SSEClientTransportOptions
151
+ transport = new SSEClientTransport(endpoint, opts)
152
+ await this.client.connect(transport)
176
153
  }
177
154
 
178
155
  if (type === 'socket') {
@@ -182,14 +159,9 @@ export class WebMcpClient {
182
159
  }
183
160
 
184
161
  if (typeof transport === 'undefined') {
185
- if (authProvider) {
186
- const createTransport = () => new StreamableHTTPClientTransport(endpoint, { authProvider })
187
- transport = await attemptConnection(this.client, authProvider.waitForOAuthCode, createTransport)
188
- } else {
189
- const opts = streamOptions(token, sessionId) as StreamableHTTPClientTransportOptions
190
- transport = new StreamableHTTPClientTransport(endpoint, opts)
191
- await this.client.connect(transport)
192
- }
162
+ const opts = streamOptions(token, sessionId) as StreamableHTTPClientTransportOptions
163
+ transport = new StreamableHTTPClientTransport(endpoint, opts)
164
+ await this.client.connect(transport)
193
165
  }
194
166
 
195
167
  this.transport = transport
@@ -7,6 +7,7 @@ import { ProviderV2 } from '@ai-sdk/provider'
7
7
  import { OpenAIProvider } from '@ai-sdk/openai'
8
8
  import { createOpenAI } from '@ai-sdk/openai'
9
9
  import { createDeepSeek } from '@ai-sdk/deepseek'
10
+ import { ExtensionClientTransport } from '../transport/ExtensionClientTransport'
10
11
 
11
12
  export const AIProviderFactories = {
12
13
  ['openai']: createOpenAI,
@@ -15,35 +16,35 @@ export const AIProviderFactories = {
15
16
 
16
17
  type ChatMethodFn = typeof streamText | typeof generateText
17
18
 
18
- /** 一个通用的ai-sdk的agent封装
19
+ /** 一个通用的ai-sdk的Agent封装
19
20
  * @summary 内部自动管理了 llm, mcpServer, ai-sdk的clients 和 tools
20
21
  * @returns 暴露了 chat, chatStream方法
21
22
  */
22
23
  export class AgentModelProvider {
23
24
  llm: ProviderV2 | OpenAIProvider
24
- /** mcpServers 允许为配置为 McpServerConfig, 或者任意的 MCPTransport
25
+ /** 当前mcpServers对象集合。键为服务器名称,值为 McpServerConfig 或任意的 MCPTransport
25
26
  * 参考: https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#initializing-an-mcp-client */
26
- mcpServers: McpServerConfig[] = []
27
- /** ai-sdk的 mcpClient */
28
- mcpClients: any[] = []
29
- /** 所有的tools */
30
- mcpTools: Array<Record<string, any>> = []
31
- /** 需要实时过滤掉的tools name*/
27
+ mcpServers: Record<string, McpServerConfig> = {}
28
+ /** 当前ai-sdk的 mcpClient 对象集合,键为服务器名称 */
29
+ mcpClients: Record<string, any> = {}
30
+ /** 当前 mcpClients 所对应的tools,键为服务器名称 */
31
+ mcpTools: Record<string, Record<string, any>> = {}
32
+ /** 需要实时过滤掉的tools name*/
32
33
  ignoreToolnames: string[] = []
33
-
34
- /** chat 时,自动更新 所有的tools 后的事件 */
34
+ /** Agent 自动更新所有的tools 后的事件 */
35
35
  onUpdatedTools: (() => void) | undefined
36
- /** 内部报错时,抛出错误事件 */
36
+ /** Agent 内部报错时,抛出的错误事件 */
37
37
  onError: ((msg: string, err?: any) => void) | undefined
38
-
39
- /** 缓存 ai-sdk response 中的 多轮会话 */
38
+ /** MCP Client 断开连接时的回调 */
39
+ onClientDisconnected?: (serverName: string, reason?: string) => void
40
+ /** 缓存 ai-sdk response 中的 多轮会话的上下文 */
40
41
  messages: any[] = []
41
42
 
42
43
  constructor({ llmConfig, mcpServers, llm }: IAgentModelProviderOption) {
43
- // 1、保存 mcpServer
44
- this.mcpServers = mcpServers || []
44
+ this.mcpServers = mcpServers || {}
45
+ this.mcpClients = {}
46
+ this.mcpTools = {}
45
47
 
46
- // 2、保存 llm
47
48
  if (llm) {
48
49
  this.llm = llm
49
50
  } else if (llmConfig) {
@@ -63,13 +64,15 @@ export class AgentModelProvider {
63
64
  }
64
65
  }
65
66
 
66
- /** 创建一个 ai-sdk的 mcpClient, 创建失败则返回 Null */
67
+ /** 创建一个 ai-sdk的 mcpClient, 创建失败则返回 null */
67
68
  private async _createOneClient(serverConfig: McpServerConfig) {
68
69
  try {
69
70
  let transport: MCPClientConfig['transport']
70
71
  // transport 一定是 streamableHttp 或者就是: ai-sdk允许的 transport
71
72
  if ('type' in serverConfig && serverConfig.type.toLocaleLowerCase() === 'streamablehttp') {
72
- transport = new StreamableHTTPClientTransport(new URL(serverConfig.url))
73
+ transport = new StreamableHTTPClientTransport(new URL((serverConfig as { url: string }).url))
74
+ } else if ('type' in serverConfig && serverConfig.type === 'extension') {
75
+ transport = new ExtensionClientTransport(serverConfig.sessionId)
73
76
  } else {
74
77
  transport = serverConfig as MCPClientConfig['transport']
75
78
  }
@@ -77,6 +80,7 @@ export class AgentModelProvider {
77
80
  const client = await createMCPClient({ transport: transport as MCPClientConfig['transport'] })
78
81
  //@ts-ignore
79
82
  client['__transport__'] = transport
83
+
80
84
  return client
81
85
  } catch (error: unknown) {
82
86
  if (this.onError) {
@@ -86,41 +90,57 @@ export class AgentModelProvider {
86
90
  return null
87
91
  }
88
92
  }
89
- /** 关闭一个client */
93
+ /** 关闭一个 mcpClient */
90
94
  private async _closeOneClient(client: any) {
91
- await client['__transport__']?.terminateSession?.()
92
- await client['__transport__']?.close?.()
93
- await client?.close?.()
95
+ try {
96
+ await client['__transport__']?.terminateSession?.()
97
+ await client['__transport__']?.close?.()
98
+ await client?.close?.()
99
+ } catch (error) {}
94
100
  }
95
- /** 创建 ai-sdk的 mcpClient, 失败则保存为null */
101
+ /** 创建所有 mcpClients */
96
102
  private async _createMpcClients() {
97
103
  // 使用 Promise.all 并行处理所有 mcpServer 项
98
- this.mcpClients = await Promise.all(
99
- this.mcpServers.map(async (server) => {
100
- return this._createOneClient(server)
104
+ const serverEntries = Object.entries(this.mcpServers)
105
+ const clients = await Promise.all(
106
+ serverEntries.map(async ([serverName, server]) => {
107
+ const client = await this._createOneClient(server)
108
+ return { serverName, client }
101
109
  })
102
110
  )
111
+ // 将结果存储到对象中,使用 serverName 作为键
112
+ this.mcpClients = {}
113
+ clients.forEach(({ serverName, client }) => {
114
+ this.mcpClients[serverName] = client
115
+ })
103
116
  }
104
- /** 创建所有 mcpClients 的 tools, 失败则保存为null */
117
+ /** 查询所有 mcpClients 的 tools, 失败则保存为null */
105
118
  private async _createMpcTools() {
106
- this.mcpTools = await Promise.all(
107
- this.mcpClients.map(async (client) => {
119
+ const clientEntries = Object.entries(this.mcpClients)
120
+ const tools = await Promise.all(
121
+ clientEntries.map(async ([serverName, client]) => {
108
122
  try {
109
- return client ? await client?.tools?.() : null
123
+ const result = client ? await client?.tools?.() : null
124
+ return { serverName, tools: result }
110
125
  } catch (error: unknown) {
111
126
  if (this.onError) {
112
127
  this.onError((error as Error)?.message || `Failed to query tools`, error)
113
128
  }
114
129
  console.error(`Failed to query tools`, error)
115
- return null
130
+ return { serverName, tools: null }
116
131
  }
117
132
  })
118
133
  )
134
+ // 将结果存储到对象中,使用 serverName 作为键
135
+ this.mcpTools = {}
136
+ tools.forEach(({ serverName, tools: toolsData }) => {
137
+ this.mcpTools[serverName] = toolsData
138
+ })
119
139
  }
120
140
  /** 关闭所有的 clients */
121
141
  async closeAll() {
122
142
  await Promise.all(
123
- this.mcpClients.map(async (client) => {
143
+ Object.values(this.mcpClients).map(async (client) => {
124
144
  try {
125
145
  await this._closeOneClient(client)
126
146
  } catch (error: unknown) {
@@ -133,58 +153,68 @@ export class AgentModelProvider {
133
153
  )
134
154
  }
135
155
 
156
+ /** 创建所有的 mcpClients,并更新它们的tools */
136
157
  async initClientsAndTools() {
137
158
  await this._createMpcClients()
138
159
  await this._createMpcTools()
160
+ this.onUpdatedTools?.()
139
161
  }
140
162
 
141
- async updateMcpServers(mcpServers: McpServerConfig[]) {
163
+ /** 全量更新所有的 mcpServers */
164
+ async updateMcpServers(mcpServers?: Record<string, McpServerConfig>) {
142
165
  await this.closeAll()
143
- this.mcpServers = mcpServers
166
+ this.mcpServers = mcpServers || this.mcpServers
144
167
  await this.initClientsAndTools()
145
168
  }
146
169
 
147
- async insertMcpServer(mcpServer: McpServerConfig) {
148
- const find = this.mcpServers.find((item: any) => 'url' in item && 'url' in mcpServer && item.url === mcpServer.url)
149
-
150
- if (!find) {
151
- this.mcpServers = [...this.mcpServers, mcpServer]
152
- const client = await this._createOneClient(mcpServer)
153
- this.mcpClients.push(client)
154
- this.mcpTools.push((await client?.tools?.()) as Record<string, any>)
155
- return true
170
+ /** 插入一个新的mcpServer,如果已经存在则返回false */
171
+ async insertMcpServer(serverName: string, mcpServer: McpServerConfig) {
172
+ // 检查是否已存在相同名称的服务器
173
+ if (this.mcpServers[serverName]) {
174
+ return false
156
175
  }
157
- return false
176
+
177
+ const client = await this._createOneClient(mcpServer)
178
+ this.mcpClients[serverName] = client
179
+ this.mcpTools[serverName] = (await client?.tools?.()) as Record<string, any>
180
+ this.mcpServers[serverName] = mcpServer
181
+ this.onUpdatedTools?.()
182
+
183
+ return true
158
184
  }
159
- /** 通过引用,删除一个 mcpServers mcpClients mcpTools ignoreToolnames */
160
- async removeMcpServer(mcpServer: McpServerConfig) {
161
- const index = this.mcpServers.findIndex((server) => server === mcpServer)
185
+ /** 通过服务器名称删除mcpServer: mcpServers mcpClients mcpTools ignoreToolnames */
186
+ async removeMcpServer(serverName: string) {
187
+ if (!this.mcpServers[serverName]) {
188
+ return
189
+ }
162
190
 
163
- // 移除
164
- this.mcpServers.splice(index, 1)
191
+ // 删除 mcpServer
192
+ delete this.mcpServers[serverName]
165
193
 
166
- // 移除
167
- const delClient = this.mcpClients[index]
168
- this.mcpClients.splice(index, 1)
194
+ // 关闭并删除 client
195
+ const delClient = this.mcpClients[serverName]
196
+ delete this.mcpClients[serverName]
169
197
  try {
170
198
  await this._closeOneClient(delClient)
171
199
  } catch (error) {}
172
200
 
173
- // 移除 tools
174
- const delTool = this.mcpTools[index]
175
- this.mcpTools.splice(index, 1)
201
+ // 删除 tools 并清理 ignoreToolnames
202
+ const delTool = this.mcpTools[serverName]
203
+ delete this.mcpTools[serverName]
176
204
 
177
- // 移除 ignoreToolnames
178
205
  if (delTool) {
179
206
  Object.keys(delTool).forEach((toolName) => {
180
207
  this.ignoreToolnames = this.ignoreToolnames.filter((name) => name !== toolName)
181
208
  })
182
209
  }
210
+
211
+ this.onUpdatedTools?.()
183
212
  }
184
213
 
185
214
  /** 创建临时允许调用的tools集合 */
186
215
  private _tempMergeTools(extraTool = {}) {
187
- const toolsResult = this.mcpTools.reduce((acc, curr) => ({ ...acc, ...curr }), {})
216
+ // 将对象的值转换为数组后再 reduce
217
+ const toolsResult = Object.values(this.mcpTools).reduce((acc, curr) => ({ ...acc, ...curr }), {})
188
218
  Object.assign(toolsResult, extraTool)
189
219
 
190
220
  this.ignoreToolnames.forEach((name) => {
@@ -203,8 +233,6 @@ export class AgentModelProvider {
203
233
 
204
234
  await this.initClientsAndTools()
205
235
 
206
- this.onUpdatedTools?.()
207
-
208
236
  const chatOptions = {
209
237
  // @ts-ignore ProviderV2 是所有llm的父类, 在每一个具体的llm 类都有一个选择model的函数用法
210
238
  model: this.llm(model),
package/agent/type.ts CHANGED
@@ -14,7 +14,11 @@ export interface IAgentModelProviderLlmConfig {
14
14
  }
15
15
 
16
16
  /** Mcp Server的配置对象 */
17
- export type McpServerConfig = { type: 'streamableHttp' | 'sse'; url: string } | { transport: MCPTransport }
17
+ export type McpServerConfig =
18
+ | { type: 'streamableHttp'; url: string }
19
+ | { type: 'sse'; url: string }
20
+ | { type: 'extension'; url: string; sessionId: string }
21
+ | { transport: MCPTransport }
18
22
 
19
23
  /** */
20
24
  export interface IAgentModelProviderOption {
@@ -25,6 +29,6 @@ export interface IAgentModelProviderOption {
25
29
  llm?: ProviderV2
26
30
  /** 代理模型提供器的大语言配置对象, 不能与 llm 同时传入 */
27
31
  llmConfig?: IAgentModelProviderLlmConfig
28
- /** Mcp Server的配置对象的集合 */
29
- mcpServers?: McpServerConfig[]
32
+ /** Mcp Server的配置对象的集合,键为服务器名称,值为配置对象 */
33
+ mcpServers?: Record<string, McpServerConfig>
30
34
  }
@@ -2,9 +2,9 @@ import { dynamicTool, jsonSchema, Tool, ToolCallOptions, ToolSet } from 'ai'
2
2
  import { WebMcpClient } from '../../WebMcpClient'
3
3
 
4
4
  /**
5
- * 快速 从官方 mcp 或 WebMcpClient 这2种client中, 抽取成 ai-sdk 所需要的 tool的对象
5
+ * 快速从官方 mcp 或 WebMcpClient 这2种client中读取 tools 数组,并转换成 ai-sdk tool的对象格式。
6
6
  * @params client 一个已连接好的 WebMcpClient
7
- * @returns ai-sdk的 tool 格式的对象。
7
+ * @returns ai-sdk的dynamicTool对象。
8
8
  */
9
9
  export const getAISDKTools = async (client: WebMcpClient): Promise<ToolSet> => {
10
10
  const tools: Record<string, Tool> = {}