@opentiny/next-sdk 0.2.9 → 0.3.0-alpha.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/README.md CHANGED
@@ -11,6 +11,9 @@
11
11
  <a href="#️-参与开发">🛠️ 参与开发</a>
12
12
  </p>
13
13
 
14
+ > [!IMPORTANT]
15
+ > **WebMCP 兼容性说明**:OpenTiny WebMCP 方案**全面兼容 Google Chrome 浏览器由 Google 官方定义的内置 WebMCP 协议**。这意味着您通过此 SDK 开发的 WebSkills 可以无缝接入浏览器原生的 AI 能力。
16
+
14
17
  ---
15
18
 
16
19
  **OpenTiny NEXT-SDKs** 是一套前端智能应用开发工具包,旨在简化 WebAgent 的集成与使用,支持多种编程语言和前端框架,帮助开发者快速实现智能化功能。
@@ -31,7 +34,7 @@
31
34
  ## ✨ 主要特性
32
35
 
33
36
  - 🎯 **简化集成**:提供简洁的 API 封装,简化与 WebAgent 服务的连接、认证等逻辑
34
- - 🔌 **MCP 协议**:完整实现 Model Context Protocol(MCP)的 Web 版本,支持浏览器端工具调用
37
+ - 🔌 **MCP 协议**:完整实现 Model Context Protocol(MCP)的 Web 版本,**全面兼容 Google Chrome 内置的 WebMCP 协议**,支持浏览器端工具调用
35
38
  - 🤖 **AI 对话组件**:提供开箱即用的 AI 对话框组件(`@opentiny/next-remoter`)
36
39
  - 🔄 **适配器层**:可将任意前端 AI 对话框组件快速接入 WebAgent 服务
37
40
  - 🌐 **多模态支持**:支持文字、语音等多模态输入,抹平不同 LLM 之间的差异
@@ -88,21 +91,27 @@ const server = new WebMcpServer({
88
91
  })
89
92
 
90
93
  // 注册工具
91
- server.registerTool('demo-tool', {
92
- title: '演示工具',
93
- description: '这是一个演示工具',
94
- inputSchema: {
95
- foo: z.string().describe('输入参数')
94
+ server.registerTool(
95
+ 'demo-tool',
96
+ {
97
+ title: '演示工具',
98
+ description: '这是一个演示工具',
99
+ inputSchema: {
100
+ foo: z.string().describe('输入参数')
101
+ }
96
102
  },
97
- }, async (params) => {
98
- console.log('接收到参数:', params)
99
- return {
100
- content: [{
101
- type: 'text',
102
- text: `已处理: ${params.foo}`
103
- }]
103
+ async (params) => {
104
+ console.log('接收到参数:', params)
105
+ return {
106
+ content: [
107
+ {
108
+ type: 'text',
109
+ text: `已处理: ${params.foo}`
110
+ }
111
+ ]
112
+ }
104
113
  }
105
- })
114
+ )
106
115
 
107
116
  // 连接 Server Transport
108
117
  await server.connect(serverTransport)
@@ -157,10 +166,7 @@ const sessionId = 'your-session-id'
157
166
  </script>
158
167
 
159
168
  <template>
160
- <tiny-remoter
161
- :session-id="sessionId"
162
- title="我的智能助手"
163
- />
169
+ <tiny-remoter :session-id="sessionId" title="我的智能助手" />
164
170
  </template>
165
171
  ```
166
172
 
@@ -185,33 +191,33 @@ const sessionId = 'your-session-id'
185
191
  </head>
186
192
  <body>
187
193
  <script>
188
- (async () => {
189
- const { WebMcpServer, createMessageChannelPairTransport, z, WebMcpClient } = WebMCP;
190
- const [serverTransport, clientTransport] = createMessageChannelPairTransport();
194
+ ;(async () => {
195
+ const { WebMcpServer, createMessageChannelPairTransport, z, WebMcpClient } = WebMCP
196
+ const [serverTransport, clientTransport] = createMessageChannelPairTransport()
191
197
 
192
- const client = new WebMcpClient();
193
- await client.connect(clientTransport);
198
+ const client = new WebMcpClient()
199
+ await client.connect(clientTransport)
194
200
  const { sessionId } = await client.connect({
195
201
  agent: true,
196
- url: "https://agent.opentiny.design/api/v1/webmcp-trial/mcp",
197
- });
202
+ url: 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
203
+ })
198
204
 
199
- const server = new WebMcpServer();
205
+ const server = new WebMcpServer()
200
206
  server.registerTool(
201
- "demo-tool",
207
+ 'demo-tool',
202
208
  {
203
- title: "演示工具",
204
- description: "一个简单工具",
205
- inputSchema: { foo: z.string() },
209
+ title: '演示工具',
210
+ description: '一个简单工具',
211
+ inputSchema: { foo: z.string() }
206
212
  },
207
213
  async (params) => {
208
- console.log("params:", params);
209
- return { content: [{ type: "text", text: `收到: ${params.foo}` }] };
214
+ console.log('params:', params)
215
+ return { content: [{ type: 'text', text: `收到: ${params.foo}` }] }
210
216
  }
211
- );
217
+ )
212
218
 
213
- await server.connect(serverTransport);
214
- })();
219
+ await server.connect(serverTransport)
220
+ })()
215
221
  </script>
216
222
  </body>
217
223
  </html>
@@ -265,14 +271,20 @@ const server = new WebMcpServer({
265
271
  })
266
272
 
267
273
  // 注册工具
268
- server.registerTool('tool-name', {
269
- title: '工具标题',
270
- description: '工具描述',
271
- inputSchema: { /* Zod schema */ }
272
- }, async (params) => {
273
- // 工具处理逻辑
274
- return { content: [{ type: 'text', text: '结果' }] }
275
- })
274
+ server.registerTool(
275
+ 'tool-name',
276
+ {
277
+ title: '工具标题',
278
+ description: '工具描述',
279
+ inputSchema: {
280
+ /* Zod schema */
281
+ }
282
+ },
283
+ async (params) => {
284
+ // 工具处理逻辑
285
+ return { content: [{ type: 'text', text: '结果' }] }
286
+ }
287
+ )
276
288
  ```
277
289
 
278
290
  ### WebMcpClient
@@ -590,17 +602,21 @@ const { sessionId } = await client.connect({
590
602
  使用 `server.registerTool()` 注册自定义工具:
591
603
 
592
604
  ```typescript
593
- server.registerTool('my-tool', {
594
- title: '我的工具',
595
- description: '工具描述',
596
- inputSchema: {
597
- param1: z.string(),
598
- param2: z.number()
605
+ server.registerTool(
606
+ 'my-tool',
607
+ {
608
+ title: '我的工具',
609
+ description: '工具描述',
610
+ inputSchema: {
611
+ param1: z.string(),
612
+ param2: z.number()
613
+ }
614
+ },
615
+ async (params) => {
616
+ // 实现工具逻辑
617
+ return { content: [{ type: 'text', text: '执行结果' }] }
599
618
  }
600
- }, async (params) => {
601
- // 实现工具逻辑
602
- return { content: [{ type: 'text', text: '执行结果' }] }
603
- })
619
+ )
604
620
  ```
605
621
 
606
622
  ### 3. 支持哪些大语言模型?
@@ -2,6 +2,7 @@ import { streamText, stepCountIs, generateText } from 'ai'
2
2
  import { MCPClientConfig, createMCPClient } from '@ai-sdk/mcp'
3
3
  import type { ToolSet } from 'ai'
4
4
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
5
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
5
6
  import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
6
7
  import type { IAgentModelProviderOption, McpServerConfig } from './type'
7
8
  import { ProviderV2 } from '@ai-sdk/provider'
@@ -81,15 +82,21 @@ export class AgentModelProvider {
81
82
  private async _createOneClient(serverConfig: McpServerConfig) {
82
83
  try {
83
84
  let transport: MCPClientConfig['transport']
84
- // transport 一定是 streamableHttp 或者就是: ai-sdk允许的 transport
85
+ // transport 一定是 streamableHttp/sse 或者就是: ai-sdk允许的 transport
85
86
  if ('type' in serverConfig && serverConfig.type.toLocaleLowerCase() === 'streamablehttp') {
86
- transport = new StreamableHTTPClientTransport(new URL((serverConfig as { url: string }).url))
87
+ const configWithHeaders = serverConfig as { url: string; headers?: Record<string, string> }
88
+ const requestInit = configWithHeaders.headers ? { headers: configWithHeaders.headers } : undefined
89
+ transport = new StreamableHTTPClientTransport(new URL(configWithHeaders.url), { requestInit })
90
+ } else if ('type' in serverConfig && serverConfig.type === 'sse') {
91
+ const configWithHeaders = serverConfig as { url: string; headers?: Record<string, string> }
92
+ const requestInit = configWithHeaders.headers ? { headers: configWithHeaders.headers } : undefined
93
+ transport = new SSEClientTransport(new URL(configWithHeaders.url), { requestInit })
87
94
  } else if ('type' in serverConfig && serverConfig.type === 'extension') {
88
95
  transport = new ExtensionClientTransport(serverConfig.sessionId)
89
96
  } else if ('transport' in serverConfig) {
90
97
  transport = serverConfig.transport
91
98
  } else {
92
- transport = serverConfig as MCPClientConfig['transport']
99
+ transport = serverConfig as unknown as MCPClientConfig['transport']
93
100
  }
94
101
 
95
102
  // 根据 useAISdkClient 配置决定使用哪种 client 创建方式
@@ -215,12 +222,18 @@ export class AgentModelProvider {
215
222
  )
216
223
  }
217
224
 
218
- /** 创建所有的 mcpClients,并更新它们的tools */
219
- async initClientsAndTools() {
220
- await this._createMpcClients()
221
- await this._createMpcTools()
222
- this.onUpdatedTools?.()
223
- }
225
+ /** 创建所有的 mcpClients,并更新它们的tools */
226
+ async initClientsAndTools() {
227
+ await this._createMpcClients()
228
+ await this._createMpcTools()
229
+ this.onUpdatedTools?.()
230
+ }
231
+
232
+ /** 仅刷新已连接 clients 的 tools(不重建 client,适合工具目录动态变化场景) */
233
+ async refreshTools() {
234
+ await this._createMpcTools()
235
+ this.onUpdatedTools?.()
236
+ }
224
237
 
225
238
  /** 全量更新所有的 mcpServers */
226
239
  async updateMcpServers(mcpServers?: Record<string, McpServerConfig>) {
@@ -813,22 +826,61 @@ export class AgentModelProvider {
813
826
  }
814
827
 
815
828
  // 否则使用原有的 function calling 模式
816
- if (!this.llm) {
817
- throw new Error('LLM is not initialized')
818
- }
819
-
820
- await this.initClientsAndTools()
821
-
822
- const allTools = this._tempMergeTools(options.tools, false)
823
-
824
- const chatOptions = {
825
- // @ts-ignore ProviderV2 是所有llm的父类, 在每一个具体的llm 类都有一个选择model的函数用法
826
- model: this.llm(model),
827
- stopWhen: stepCountIs(maxSteps),
828
- ...options,
829
- tools: allTools,
830
- activeTools: this._getActiveToolNames(allTools),
831
- }
829
+ if (!this.llm) {
830
+ throw new Error('LLM is not initialized')
831
+ }
832
+
833
+ await this.initClientsAndTools()
834
+
835
+ const extraTools = (options.tools || {}) as ToolSet
836
+ const allTools = this._tempMergeTools(extraTools, false)
837
+
838
+ // tools 保持“同一个对象引用”并在每个 step 前同步:
839
+ // - prepareStep 只能控制 activeTools,不能直接返回 tools
840
+ // - 因此需要在进入每个 step 前,把最新 mcpTools 合并回当前 tools 对象
841
+ // - 这样 activeTools 新增的工具名在当步就能命中真实工具定义
842
+ const syncToolsForStep = () => {
843
+ const latestTools = this._tempMergeTools(extraTools, false)
844
+
845
+ // upsert
846
+ Object.entries(latestTools).forEach(([name, tool]) => {
847
+ ;(allTools as any)[name] = tool
848
+ })
849
+
850
+ // remove stale
851
+ Object.keys(allTools).forEach((name) => {
852
+ if (!(name in latestTools)) {
853
+ delete (allTools as any)[name]
854
+ }
855
+ })
856
+ }
857
+
858
+ const userPrepareStep = (options as any).prepareStep
859
+ const wrappedPrepareStep = async (stepOptions: any) => {
860
+ syncToolsForStep()
861
+
862
+ const latestActiveTools = this._getActiveToolNames(allTools)
863
+ const userStepPatch = typeof userPrepareStep === 'function' ? await userPrepareStep(stepOptions) : undefined
864
+ const safeUserStepPatch =
865
+ userStepPatch && typeof userStepPatch === 'object' ? userStepPatch : ({} as Record<string, any>)
866
+
867
+ return {
868
+ ...safeUserStepPatch,
869
+ activeTools: Array.isArray(safeUserStepPatch.activeTools)
870
+ ? safeUserStepPatch.activeTools.filter((name: string) => latestActiveTools.includes(name))
871
+ : latestActiveTools
872
+ }
873
+ }
874
+
875
+ const chatOptions = {
876
+ // @ts-ignore ProviderV2 是所有llm的父类, 在每一个具体的llm 类都有一个选择model的函数用法
877
+ model: this.llm(model),
878
+ stopWhen: stepCountIs(maxSteps),
879
+ ...options,
880
+ tools: allTools,
881
+ prepareStep: wrappedPrepareStep,
882
+ activeTools: this._getActiveToolNames(allTools)
883
+ }
832
884
 
833
885
  // 保存最后一条 user 消息,用于后续缓存
834
886
  let lastUserMessage: any = null
package/agent/type.ts CHANGED
@@ -35,9 +35,9 @@ export type IAgentModelProviderLlmConfig = LlmFactoryConfig | LlmInstanceConfig
35
35
 
36
36
  /** Mcp Server的配置对象 */
37
37
  export type McpServerConfig =
38
- | { type: 'streamableHttp'; url: string; useAISdkClient?: boolean }
39
- | { type: 'sse'; url: string; useAISdkClient?: boolean }
40
- | { type: 'extension'; url: string; sessionId: string; useAISdkClient?: boolean }
38
+ | { type: 'streamableHttp'; url: string; useAISdkClient?: boolean; headers?: Record<string, string> }
39
+ | { type: 'sse'; url: string; useAISdkClient?: boolean; headers?: Record<string, string> }
40
+ | { type: 'extension'; url: string; sessionId: string; useAISdkClient?: boolean; headers?: Record<string, string> }
41
41
  | { type: 'local'; transport: MCPTransport; useAISdkClient?: boolean }
42
42
 
43
43
  /** */
package/core.ts ADDED
@@ -0,0 +1,28 @@
1
+ // 核心导出,仅包含无 DOM 依赖(Node.js / Service Worker 安全)的内容
2
+ import Ajv from 'ajv'
3
+ export { Ajv }
4
+ export { z } from 'zod'
5
+ export { AuthClientProvider } from '@opentiny/next'
6
+ export { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
7
+ export { UriTemplate } from '@modelcontextprotocol/sdk/shared/uriTemplate.js'
8
+ export { completable } from '@modelcontextprotocol/sdk/server/completable.js'
9
+ export { getDisplayName } from '@modelcontextprotocol/sdk/shared/metadataUtils.js'
10
+ export { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
11
+ export type * from 'zod'
12
+ export type * from '@opentiny/next'
13
+ export type * from '@modelcontextprotocol/sdk/types.js'
14
+ export type * from '@modelcontextprotocol/sdk/shared/protocol.js'
15
+ export type * from '@modelcontextprotocol/sdk/shared/transport.js'
16
+ export type * from '@modelcontextprotocol/sdk/client/sse.js'
17
+ export type * from '@modelcontextprotocol/sdk/client/streamableHttp.js'
18
+ export type * from '@modelcontextprotocol/sdk/server/mcp.js'
19
+
20
+ export * from './WebMcpServer'
21
+ export * from './WebMcpClient'
22
+
23
+ // 浏览器扩展自定义传输层(不包含对 window 依赖的模块)
24
+ export * from './transport/ExtensionClientTransport'
25
+
26
+ export { AgentModelProvider } from './agent/AgentModelProvider'
27
+ export { getAISDKTools } from './agent/utils/getAISDKTools'
28
+ export type * from './agent/type'