@opentiny/next 0.1.4 → 0.2.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.
package/README.md CHANGED
@@ -89,349 +89,6 @@ state.client = client;
89
89
 
90
90
  请注意:创建 `MessageChannelServerTransport` 实例必须在创建 `MessageChannelClientTransport` 实例之前,确保客户端连接之前服务端已经开始监听。由于 iframe、Web Worker 等代码运行通常在浏览器主线程之后,所以上述示例代码执行顺序一般是先创建 `MessageChannelServerTransport` 实例,后创建 `MessageChannelClientTransport` 实例。
91
91
 
92
- ### SSE (Server-Sent Events) Client Proxy
93
-
94
- 使用 `MessageChannel API` 创建的 `Client` 客户端实例运行在浏览器环境中,为了能够被后端服务的 AI 调用,需要借助 `createSseProxy` 方法创建该 `Client` 的代理。
95
-
96
- ```typescript
97
- import { createSseProxy, ProxySSEClientTransport } from '@opentiny/next';
98
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
99
-
100
- // 定义页面的状态
101
- const state: {
102
- sseTransport: ProxySSEClientTransport | undefined;
103
- client: Client | undefined;
104
- } = {
105
- sseTransport: undefined,
106
- client: undefined
107
- };
108
-
109
- // 使用 SSE 代理连接到服务端
110
- const connectSseProxy = async () => {
111
- if (!state.sseTransport) {
112
- const { transport, sessionId } = await createSseProxy({
113
- client: state.client as Client,
114
- url: location + 'agent/sse',
115
- token: process.env.USER_TOKEN as string
116
- });
117
-
118
- state.sseTransport = transport;
119
- }
120
- };
121
-
122
- // 使用 SSE 代理调用工具方法
123
- const callSseProxy = async (question: string) => {
124
- if (!state.sseTransport) {
125
- console.error('You need to connect SSE Proxy first!');
126
- return;
127
- }
128
-
129
- const sessionId = state.sseTransport.sessionId;
130
- return await request('/question', { question, sessionId });
131
- };
132
-
133
- // 发送 HTTP 请求
134
- const request = async (url: string, data: string) => {
135
- const response = await fetch(url, {
136
- method: 'POST',
137
- headers: {
138
- 'Content-Type': 'application/json',
139
- Authorization: `Bearer ${process.env.USER_TOKEN}`
140
- },
141
- body: JSON.stringify(data)
142
- });
143
-
144
- if (!response.ok) {
145
- throw new Error(response.statusText);
146
- }
147
-
148
- return await response.json();
149
- };
150
- ```
151
-
152
- 请注意:在调用 `callSseProxy` 之前需要调用 `connectSseProxy`,只有代理连接成功后,后端服务的 AI 才能通过该代理调用浏览器环境下的 `Client` 客户端的工具方法。为了让后端服务能够找到当前浏览器的 `Client` 客户端连接,需要向后端服务传递 `sseTransport` 的 `sessionId`。
153
-
154
- ### Streamable HTTP Client Proxy
155
-
156
- 使用 `MessageChannel API` 创建的 `Client` 客户端实例运行在浏览器环境中,为了能够被后端服务的 AI 调用,需要借助 `createStreamProxy` 方法创建该 `Client` 的代理。
157
-
158
- ```typescript
159
- import { createStreamProxy, ProxyStreamClientTransport } from '@opentiny/next';
160
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
161
-
162
- // 定义页面的状态
163
- const state: {
164
- streamTransport: ProxyStreamClientTransport | undefined;
165
- client: Client | undefined;
166
- } = {
167
- streamTransport: undefined,
168
- client: undefined
169
- };
170
-
171
- // 使用 Stream 代理连接到服务端
172
- const connectStreamProxy = async () => {
173
- if (!state.streamTransport) {
174
- const { transport, sessionId } = await createStreamProxy({
175
- client: state.client as Client,
176
- url: location + 'agent/mcp',
177
- token: process.env.USER_TOKEN as string
178
- });
179
-
180
- state.streamTransport = transport;
181
- }
182
- };
183
-
184
- // 使用 Streamable HTTP 代理调用工具方法
185
- const callStreamProxy = async (question: string) => {
186
- if (!state.streamTransport) {
187
- console.error('You need to connect Streamable HTTP Proxy first!');
188
- return;
189
- }
190
-
191
- const sessionId = state.streamTransport.sessionId;
192
- return await request('/question', { question, sessionId });
193
- };
194
-
195
- // 关闭 Streamable HTTP 代理连接
196
- const closeStreamProxy = async () => {
197
- if (state.streamTransport) {
198
- await state.streamTransport.terminateSession();
199
- state.streamTransport = undefined;
200
- }
201
- };
202
-
203
- // 在页面窗口装载时注册关闭 Streamable HTTP 连接的事件
204
- window.addEventListener('beforeunload', closeStreamProxy);
205
-
206
- // 在页面窗口卸载时移除注册关闭 Streamable HTTP 连接的事件
207
- window.removeEventListener('beforeunload', closeStreamProxy);
208
- ```
209
-
210
- 请注意:在调用 `callStreamProxy` 之前需要调用 `connectStreamProxy`,只有代理连接成功后,后端服务的 AI 才能通过该代理调用浏览器环境下的 `Client` 客户端的工具方法。为了让后端服务能够找到当前浏览器的 `Client` 客户端连接,需要向后端服务传递 `streamTransport` 的 `sessionId`。
211
-
212
- 与 `SSE` 的连接可以自动关闭不同,`Streamable HTTP` 的连接需要手动关闭,即要调用 `streamTransport.terminateSession()` 方法来中断连接。
213
-
214
- ## 服务端 API (server.js)
215
-
216
- 服务端 API 主要用于在 Node.js 环境中创建 MCP 服务端,处理来自客户端的连接请求。
217
-
218
- ### SSE (Server-Sent Events) Server Proxy
219
-
220
- 以下是 Express 环境 Node.js 服务端连接代理的示例代码:
221
-
222
- ```typescript
223
- import express from 'express';
224
- import session from 'express-session';
225
- import cors from 'cors';
226
- import { auth, useProxyHandles } from '@opentiny/next/server.js';
227
- import type { Request, Response } from 'express';
228
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
229
-
230
- const { handleSseProxy, handleSseInspector, handleSseMessage, clients } = useProxyHandles();
231
-
232
- const app = express();
233
-
234
- // 启用跨域资源共享
235
- app.use(cors());
236
-
237
- // 获取代理背后真实的 IP 地址
238
- app.set('trust proxy', 1);
239
-
240
- // 配置 session 中间件,支持会话管理
241
- app.use(
242
- session({
243
- secret: process.env.SECRET_KEY as string, // 会话密钥
244
- cookie: { maxAge: 60000 }, // 会话有效期
245
- resave: false,
246
- saveUninitialized: true
247
- })
248
- );
249
-
250
- // 获取所有客户端的 sessionId
251
- app.get('/list', async (req: Request, res: Response) => {
252
- const sessions: Record<string, object> = {};
253
- for (const sessionId in clients) {
254
- const { user, device, type } = clients[sessionId];
255
- sessions[sessionId] = { user, device, type };
256
- }
257
- res.json(sessions);
258
- });
259
-
260
- // 启用认证中间件
261
- app.use(auth({ secret: process.env.SECRET_KEY as string }));
262
-
263
- // MCP inspector url http://localhost:5173/agent/inspector?sessionId=
264
- app.get('/inspector', async (req: Request, res: Response) => {
265
- try {
266
- await handleSseInspector(req, res, '/agent/messages');
267
- } catch (error) {
268
- console.error('AI Agent inspector error:', (error as Error).message);
269
- }
270
- });
271
-
272
- // SSE 端点,支持 inspector 和 proxy 两种模式,inspector url http://localhost:8001/sse?sessionId=
273
- app.get('/sse', async (req: Request, res: Response) => {
274
- try {
275
- if (req.query.sessionId) {
276
- await handleSseInspector(req, res, '/messages');
277
- } else {
278
- await handleSseProxy(req, res, '/agent/messages');
279
- }
280
- } catch (error) {
281
- console.error('AI Agent proxy error:', (error as Error).message);
282
- }
283
- });
284
-
285
- // 消息转发端点,根据 sessionId 找到对应 transport 处理消息
286
- app.post('/messages', async (req: Request, res: Response) => {
287
- try {
288
- await handleSseMessage(req, res);
289
- } catch (error) {
290
- console.error('AI Agent message error:', (error as Error).message);
291
- }
292
- });
293
-
294
- // 用户提问端点,支持带 sessionId 的上下文调用
295
- app.post('/question', express.json(), async (req: Request, res: Response) => {
296
- try {
297
- const sessionId = req.body.sessionId as string;
298
- const client = clients[sessionId]?.client;
299
- const question = req.body.question as string;
300
- const answer = await ask(question, client); // 需自行实现 AI 调用 Client 的工具
301
- res.json({ answer });
302
- } catch (error) {
303
- console.error('AI Agent question error:', (error as Error).message);
304
- }
305
- });
306
-
307
- app.listen(8001, () => {
308
- console.log('AI Agent listening on port 8001');
309
- });
310
- ```
311
-
312
- ### Streamable HTTP Server Proxy
313
-
314
- 以下是 Express 环境 Node.js 服务端连接代理的示例代码:
315
-
316
- ```typescript
317
- import express from 'express';
318
- import session from 'express-session';
319
- import cors from 'cors';
320
- import { auth, useProxyHandles } from '@opentiny/next/server.js';
321
- import type { Request, Response } from 'express';
322
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
323
-
324
- const { handleStreamRequest, handleStreamInspector, clients } = useProxyHandles();
325
-
326
- const app = express();
327
-
328
- // 启用跨域资源共享
329
- app.use(cors());
330
-
331
- // 获取代理背后真实的 IP 地址
332
- app.set('trust proxy', 1);
333
-
334
- // 配置 session 中间件,支持会话管理
335
- app.use(
336
- session({
337
- secret: process.env.SECRET_KEY as string, // 会话密钥
338
- cookie: { maxAge: 60000 }, // 会话有效期
339
- resave: false,
340
- saveUninitialized: true
341
- })
342
- );
343
-
344
- // 获取所有客户端的 sessionId
345
- app.get('/list', async (req: Request, res: Response) => {
346
- const sessions: Record<string, object> = {};
347
- for (const sessionId in clients) {
348
- const { user, device, type } = clients[sessionId];
349
- sessions[sessionId] = { user, device, type };
350
- }
351
- res.json(sessions);
352
- });
353
-
354
- // 启用认证中间件
355
- app.use(auth({ secret: process.env.SECRET_KEY as string }));
356
-
357
- // 处理 Streamable HTTP 服务端 POST/GET/DELETE 连接
358
- // 可以使用 MCP inspector 连接调试,方式与 SSE 连接相同,如下:
359
- // http://localhost:8001/mcp?sessionId= 或 http://localhost:5173/agent/mcp?sessionId=
360
- app.all('/mcp', express.json(), async (req: Request, res: Response) => {
361
- try {
362
- if (req.query.sessionId) {
363
- await handleStreamInspector(req, res);
364
- } else {
365
- await handleStreamRequest(req, res);
366
- }
367
- } catch (error) {
368
- console.error('AI Agent mcp error:', (error as Error).message);
369
- }
370
- });
371
-
372
- // 用户提问端点,支持带 sessionId 的上下文调用
373
- app.post('/question', express.json(), async (req: Request, res: Response) => {
374
- try {
375
- const sessionId = req.body.sessionId as string;
376
- const client = clients[sessionId]?.client;
377
- const question = req.body.question as string;
378
- const answer = await ask(question, client); // 需自行实现 AI 调用 Client 的工具
379
- res.json({ answer });
380
- } catch (error) {
381
- console.error('AI Agent question error:', (error as Error).message);
382
- }
383
- });
384
-
385
- app.listen(8001, () => {
386
- console.log('AI Agent listening on port 8001');
387
- });
388
- ```
389
-
390
- ## Vite 工程配置示例
391
-
392
- 以上示例代码均基于 Vite 工程配置运行,增加了环境变量以及代理转发等,以下是 `vite.config.ts` 示例:
393
-
394
- ```typescript
395
- import { defineConfig } from 'vite';
396
- import vue from '@vitejs/plugin-vue';
397
- import dotenv from 'dotenv';
398
- import path from 'path';
399
-
400
- // Vite 配置文件
401
- export default defineConfig(() => {
402
- // 加载 .env 文件中的环境变量
403
- dotenv.config({ path: '.env' });
404
-
405
- return {
406
- // 注入环境变量到前端代码
407
- define: {
408
- 'process.env': {
409
- USER_TOKEN: process.env.USER_TOKEN
410
- }
411
- },
412
- plugins: [vue()],
413
- server: {
414
- proxy: {
415
- // 代理 /agent 开头的请求到本地 8001 端口(AI Agent 服务)
416
- '/agent': {
417
- target: 'http://localhost:8001',
418
- changeOrigin: true,
419
- rewrite: (path) => path.replace(/^\/agent/, '')
420
- }
421
- }
422
- }
423
- };
424
- });
425
- ```
426
-
427
- ## 注意事项
428
-
429
- 1. 确保在服务端使用时安装 `express` 作为依赖
430
- 2. 使用 JWT 令牌认证时,请保护好密钥
431
- 3. 在生产环境中,应该设置适当的 CORS 配置
432
- 4. SSE 连接会保持打开状态,请确保适当处理资源释放
433
- 5. 使用 Streamable HTTP 时,记得在会话结束时调用 `terminateSession()` 方法关闭连接
434
-
435
92
  ## 许可证
436
93
 
437
94
  MIT
package/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
3
3
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
- import { SSEClientTransport, SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';
5
- import { StreamableHTTPClientTransport, StreamableHTTPClientTransportOptions } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
6
6
 
7
7
  /**
8
8
  * MessageChannelTransport 是一个用于在浏览器上下文之间传输消息的类。
@@ -94,69 +94,11 @@ declare class MessageChannelServerTransport extends MessageChannelTransport {
94
94
  */
95
95
  declare const createTransportPair: () => [MessageChannelTransport, MessageChannelTransport];
96
96
 
97
- /**
98
- * ProxySSEClientTransport(SSE 代理客户端传输类)
99
- * 该类用于通过 Server-Sent Events(SSE)协议从服务端接收消息,主要应用于浏览器等客户端环境,实现与服务端的实时消息通信。
100
- * 消息到达时会自动调用 forward.js 进行 method 分发交由代理处理。
101
- */
102
- declare class ProxySSEClientTransport extends SSEClientTransport {
103
- _client: Client;
104
- /**
105
- * 构造函数
106
- * @param url - SSE 服务端地址
107
- * @param opts - 传输配置参数(如认证、请求头等)
108
- * @param client - 客户端代理实例
109
- */
110
- constructor(url: URL, opts: SSEClientTransportOptions, client: Client);
111
- /**
112
- * 启动 SSE 连接或进行认证
113
- * 重写父类方法,增加消息自动分发处理逻辑
114
- *
115
- * 收到 SSE 消息后,自动解析并转发到 forward.js 进行 method 分发。
116
- * 若 forward 返回 undefined,则调用自定义 onmessage 回调。
117
- */
118
- _startOrAuth(): Promise<void>;
119
- /**
120
- * 获取当前会话 sessionId
121
- */
122
- get sessionId(): string;
123
- }
124
-
125
- /**
126
- * SSE 连接的启动或认证选项
127
- */
128
- interface StartSSEOptions {
129
- resumptionToken?: string;
130
- onresumptiontoken?: (token: string) => void;
131
- replayMessageId?: string | number;
132
- }
133
- /**
134
- * ProxyStreamClientTransport(Streamable HTTP 代理客户端传输类)
135
- * 该类用于通过 Streamable HTTP 协议从服务端接收消息,主要应用于浏览器等客户端环境,实现与服务端的实时消息通信。
136
- * 消息到达时会自动调用 forward.js 进行 method 分发和业务处理。
137
- */
138
- declare class ProxyStreamClientTransport extends StreamableHTTPClientTransport {
139
- _client: Client;
140
- /**
141
- * 构造函数
142
- * @param url - Streamable HTTP 服务端地址
143
- * @param opts - 传输配置参数(如认证、请求头等)
144
- * @param client - 客户端代理实例
145
- */
146
- constructor(url: URL, opts: StreamableHTTPClientTransportOptions, client: Client);
147
- /**
148
- * 处理 SSE 数据流
149
- * 重写父类方法,增加消息自动分发处理逻辑
150
- *
151
- * 收到 SSE 消息后,自动解析并转发到 forward.js 进行 method 分发。
152
- * 若 forward 返回 undefined,则调用自定义 onmessage 回调。
153
- *
154
- * @param stream - SSE 可读数据流
155
- * @param options - 启动 SSE 的配置参数
156
- */
157
- _handleSseStream(stream: ReadableStream, options: StartSSEOptions): void;
97
+ declare module '@modelcontextprotocol/sdk/client/sse.js' {
98
+ interface SSEClientTransport {
99
+ sessionId: string;
100
+ }
158
101
  }
159
-
160
102
  /**
161
103
  * 客户端代理选项接口
162
104
  */
@@ -173,6 +115,10 @@ interface ClientProxyOption {
173
115
  * 代理服务器的身份验证令牌
174
116
  */
175
117
  token: string;
118
+ /**
119
+ * 可选的会话 ID,用于标识当前会话
120
+ */
121
+ sessionId?: string;
176
122
  }
177
123
  /**
178
124
  * 创建一个 SSE 客户端代理
@@ -181,7 +127,7 @@ interface ClientProxyOption {
181
127
  * @returns - 返回一个包含 transport 和 sessionId 的对象
182
128
  */
183
129
  declare const createSseProxy: (option: ClientProxyOption) => Promise<{
184
- transport: ProxySSEClientTransport;
130
+ transport: SSEClientTransport;
185
131
  sessionId: string;
186
132
  }>;
187
133
  /**
@@ -191,7 +137,7 @@ declare const createSseProxy: (option: ClientProxyOption) => Promise<{
191
137
  * @returns - 返回一个包含 transport 和 sessionId 的对象
192
138
  */
193
139
  declare const createStreamProxy: (option: ClientProxyOption) => Promise<{
194
- transport: ProxyStreamClientTransport;
140
+ transport: StreamableHTTPClientTransport;
195
141
  sessionId: string;
196
142
  }>;
197
143
 
@@ -200,21 +146,22 @@ declare const createStreamProxy: (option: ClientProxyOption) => Promise<{
200
146
  * 用于为 SSE 连接和 HTTP 请求统一设置认证头(Authorization)。
201
147
  *
202
148
  * @param token - 用于认证的 Bearer Token。
149
+ * @param sessionId - 可选的 sessionId,用于标识会话。
203
150
  * @returns 返回传输配置对象,包含 requestInit 和 eventSourceInit 两部分:
204
151
  * - requestInit:fetch 请求的初始化配置,自动带上认证头。
205
152
  * - eventSourceInit:自定义 fetch 方法,确保 SSE 连接也带上认证头。
206
153
  */
207
- declare const sseOptions: (token: string) => {
154
+ declare const sseOptions: (token: string, sessionId?: string) => {
208
155
  requestInit: {
209
156
  headers: {
210
157
  Authorization: string;
158
+ "sse-session-id": string;
211
159
  };
212
160
  };
213
161
  eventSourceInit: {
214
162
  fetch: (input: string | URL, init?: RequestInit) => Promise<Response>;
215
163
  };
216
164
  };
217
-
218
165
  /**
219
166
  * 生成 StreamableHTTPServerTransport 构造函数所需的第二个可选参数(传输配置)。
220
167
  * 用于为 Streamable HTTP 连接和 HTTP 请求统一设置认证头(Authorization)。
@@ -224,7 +171,7 @@ declare const sseOptions: (token: string) => {
224
171
  * @returns 返回传输配置对象,包含以下内容:
225
172
  * - requestInit:fetch 请求的初始化配置,自动带上认证头,并设置 Streamable HTTP 代理的会话 ID。
226
173
  */
227
- declare const streamOptions: (token: string, sessionId?: `${string}-${string}-${string}-${string}-${string}`) => {
174
+ declare const streamOptions: (token: string, sessionId?: string) => {
228
175
  requestInit: {
229
176
  headers: {
230
177
  Authorization: string;
@@ -233,5 +180,12 @@ declare const streamOptions: (token: string, sessionId?: `${string}-${string}-${
233
180
  };
234
181
  };
235
182
 
236
- export { MessageChannelClientTransport, MessageChannelServerTransport, MessageChannelTransport, ProxySSEClientTransport, ProxyStreamClientTransport, createSseProxy, createStreamProxy, createTransportPair, sseOptions, streamOptions };
183
+ /**
184
+ * 兼容安全策略的 UUID 生成方法,支持浏览器和 Node.js 环境
185
+ *
186
+ * @returns 返回一个 UUID 字符串
187
+ */
188
+ declare const generateUUID: () => string;
189
+
190
+ export { MessageChannelClientTransport, MessageChannelServerTransport, MessageChannelTransport, createSseProxy, createStreamProxy, createTransportPair, generateUUID, sseOptions, streamOptions };
237
191
  export type { ClientProxyOption };