@opentiny/next 0.0.1 → 0.1.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 +436 -1
- package/client.d.ts +237 -0
- package/client.js +343 -265
- package/package.json +11 -2
- package/server.d.ts +95 -0
- package/server.js +224 -174
package/README.md
CHANGED
|
@@ -1 +1,436 @@
|
|
|
1
|
-
OpenTiny NEXT
|
|
1
|
+
# OpenTiny NEXT
|
|
2
|
+
|
|
3
|
+
OpenTiny NEXT 是一个基于 Model Context Protocol(MCP)的 TypeScript 库,提供了多种传输方式来支持 MCP 客户端与服务端的通信。本库支持三种主要的传输方式:
|
|
4
|
+
|
|
5
|
+
1. MessageChannel API - 用于浏览器内部不同上下文之间的通信
|
|
6
|
+
2. SSE (Server-Sent Events) Client Proxy - 基于 HTTP 长连接实现单向数据推送的 Client 连接代理
|
|
7
|
+
3. Streamable HTTP Client Proxy - 通过分块传输编码实现任意数据的流式传输的 Client 连接代理
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @opentiny/next
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 客户端 API (client.js)
|
|
16
|
+
|
|
17
|
+
客户端 API 主要用于在浏览器环境中的 MCP 通信。
|
|
18
|
+
|
|
19
|
+
### MessageChannel API
|
|
20
|
+
|
|
21
|
+
#### 在同一浏览器窗口内互相通信的场景
|
|
22
|
+
|
|
23
|
+
使用 `createTransportPair` 创建一对可互通的 Transport 服务的和客户端实例来进行通信。
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
27
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
28
|
+
import { createTransportPair } from '@opentiny/next';
|
|
29
|
+
|
|
30
|
+
// 创建一对可互通的 Transport 实例
|
|
31
|
+
const [serverTransport, clientTransport] = createTransportPair();
|
|
32
|
+
|
|
33
|
+
// 创建 MCP 服务端和客户端实例
|
|
34
|
+
const capabilities = { prompts: {}, resources: {}, tools: {}, logging: {} };
|
|
35
|
+
const server = new McpServer({ name: 'mcp-server', version: '1.0.0' }, { capabilities });
|
|
36
|
+
const client = new Client({ name: 'mcp-client', version: '1.0.0' }, { capabilities });
|
|
37
|
+
|
|
38
|
+
// 建立服务端和客户端的通信连接
|
|
39
|
+
await server.connect(serverTransport);
|
|
40
|
+
await client.connect(clientTransport);
|
|
41
|
+
|
|
42
|
+
// 将客户端实例存储到状态中
|
|
43
|
+
state.client = client;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### 在浏览器主线程与iframe、Web Worker等互相通信的场景
|
|
47
|
+
|
|
48
|
+
使用 `MessageChannelServerTransport` 和 `MessageChannelClientTransport` 创建用于监听的 Transport 服务端实例,以及用于连接的 Transport 客户端实例来进行通信。
|
|
49
|
+
|
|
50
|
+
以下是在浏览器主线程的代码:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
54
|
+
import { MessageChannelServerTransport } from '@opentiny/next';
|
|
55
|
+
|
|
56
|
+
// 创建用于监听的 Transport 服务端实例
|
|
57
|
+
const serverTransport = new MessageChannelServerTransport('endpoint');
|
|
58
|
+
|
|
59
|
+
// 创建 MCP 服务端实例
|
|
60
|
+
const capabilities = { prompts: {}, resources: {}, tools: {}, logging: {} };
|
|
61
|
+
const server = new McpServer({ name: 'mcp-server', version: '1.0.0' }, { capabilities });
|
|
62
|
+
|
|
63
|
+
// 监听 endpoint 端点,等待客户端连接
|
|
64
|
+
await serverTransport.listen();
|
|
65
|
+
|
|
66
|
+
// 建立服务端和客户端的通信连接
|
|
67
|
+
await server.connect(serverTransport);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
以下是在 iframe、Web Worker 的代码:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
74
|
+
import { MessageChannelClientTransport } from '@opentiny/next';
|
|
75
|
+
|
|
76
|
+
// 创建用于连接的 Transport 客户端实例
|
|
77
|
+
const clientTransport = new MessageChannelClientTransport('endpoint');
|
|
78
|
+
|
|
79
|
+
// 创建 MCP 客户端实例
|
|
80
|
+
const capabilities = { prompts: {}, resources: {}, tools: {}, logging: {} };
|
|
81
|
+
const client = new Client({ name: 'mcp-client', version: '1.0.0' }, { capabilities });
|
|
82
|
+
|
|
83
|
+
// 建立服务端和客户端的通信连接
|
|
84
|
+
await client.connect(clientTransport);
|
|
85
|
+
|
|
86
|
+
// 将客户端实例存储到状态中
|
|
87
|
+
state.client = client;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
请注意:创建 `MessageChannelServerTransport` 实例必须在创建 `MessageChannelClientTransport` 实例之前,确保客户端连接之前服务端已经开始监听。由于 iframe、Web Worker 等代码运行通常在浏览器主线程之后,所以上述示例代码执行顺序一般是先创建 `MessageChannelServerTransport` 实例,后创建 `MessageChannelClientTransport` 实例。
|
|
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
|
+
// 配置 session 中间件,支持会话管理
|
|
238
|
+
app.use(
|
|
239
|
+
session({
|
|
240
|
+
secret: process.env.SECRET_KEY as string, // 会话密钥
|
|
241
|
+
cookie: { maxAge: 60000 }, // 会话有效期
|
|
242
|
+
resave: false,
|
|
243
|
+
saveUninitialized: true
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// 获取所有客户端的 sessionId
|
|
248
|
+
app.get('/list', async (req: Request, res: Response) => {
|
|
249
|
+
const sessions: Record<string, string> = {};
|
|
250
|
+
for (const sessionId in clients) {
|
|
251
|
+
sessions[sessionId] = clients[sessionId].user.userId as string;
|
|
252
|
+
}
|
|
253
|
+
res.json(sessions);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// 启用认证中间件
|
|
257
|
+
app.use(auth({ secret: process.env.SECRET_KEY as string }));
|
|
258
|
+
|
|
259
|
+
// MCP inspector url http://localhost:5173/agent/inspector?sessionId=
|
|
260
|
+
app.get('/inspector', async (req: Request, res: Response) => {
|
|
261
|
+
console.log('AI Agent inspector auth:', req.user?.userId);
|
|
262
|
+
try {
|
|
263
|
+
await handleSseInspector(req, res, '/agent/messages');
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('AI Agent inspector error:', (error as Error).message);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// SSE 端点,支持 inspector 和 proxy 两种模式,inspector url http://localhost:8001/sse?sessionId=
|
|
270
|
+
app.get('/sse', async (req: Request, res: Response) => {
|
|
271
|
+
console.log('AI Agent sse auth:', req.user?.userId);
|
|
272
|
+
try {
|
|
273
|
+
if (req.query.sessionId) {
|
|
274
|
+
await handleSseInspector(req, res, '/messages');
|
|
275
|
+
} else {
|
|
276
|
+
await handleSseProxy(req, res, '/agent/messages');
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error('AI Agent proxy error:', (error as Error).message);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// 消息转发端点,根据 sessionId 找到对应 transport 处理消息
|
|
284
|
+
app.post('/messages', async (req: Request, res: Response) => {
|
|
285
|
+
console.log('AI Agent messages auth:', req.user?.userId);
|
|
286
|
+
try {
|
|
287
|
+
await handleSseMessage(req, res);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error('AI Agent message error:', (error as Error).message);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// 用户提问端点,支持带 sessionId 的上下文调用
|
|
294
|
+
app.post('/question', express.json(), async (req: Request, res: Response) => {
|
|
295
|
+
try {
|
|
296
|
+
const sessionId = req.body.sessionId as string;
|
|
297
|
+
const client = clients[sessionId]?.client;
|
|
298
|
+
const question = req.body.question as string;
|
|
299
|
+
const answer = await ask(question, client); // 需自行实现 AI 调用 Client 的工具
|
|
300
|
+
res.json({ answer });
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error('AI Agent question error:', (error as Error).message);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
app.listen(8001, () => {
|
|
307
|
+
console.log('AI Agent listening on port 8001');
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Streamable HTTP Server Proxy
|
|
312
|
+
|
|
313
|
+
以下是 Express 环境 Node.js 服务端连接代理的示例代码:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import express from 'express';
|
|
317
|
+
import session from 'express-session';
|
|
318
|
+
import cors from 'cors';
|
|
319
|
+
import { auth, useProxyHandles } from '@opentiny/next/server.js';
|
|
320
|
+
import type { Request, Response } from 'express';
|
|
321
|
+
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
322
|
+
|
|
323
|
+
const { handleStreamRequest, handleStreamInspector, clients } = useProxyHandles();
|
|
324
|
+
|
|
325
|
+
const app = express();
|
|
326
|
+
|
|
327
|
+
// 启用跨域资源共享
|
|
328
|
+
app.use(cors());
|
|
329
|
+
|
|
330
|
+
// 配置 session 中间件,支持会话管理
|
|
331
|
+
app.use(
|
|
332
|
+
session({
|
|
333
|
+
secret: process.env.SECRET_KEY as string, // 会话密钥
|
|
334
|
+
cookie: { maxAge: 60000 }, // 会话有效期
|
|
335
|
+
resave: false,
|
|
336
|
+
saveUninitialized: true
|
|
337
|
+
})
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// 获取所有客户端的 sessionId
|
|
341
|
+
app.get('/list', async (req: Request, res: Response) => {
|
|
342
|
+
const sessions: Record<string, string> = {};
|
|
343
|
+
for (const sessionId in clients) {
|
|
344
|
+
sessions[sessionId] = clients[sessionId].user.userId as string;
|
|
345
|
+
}
|
|
346
|
+
res.json(sessions);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// 启用认证中间件
|
|
350
|
+
app.use(auth({ secret: process.env.SECRET_KEY as string }));
|
|
351
|
+
|
|
352
|
+
// 处理 Streamable HTTP 服务端 POST/GET/DELETE 连接
|
|
353
|
+
// 可以使用 MCP inspector 连接调试,方式与 SSE 连接相同,如下:
|
|
354
|
+
// http://localhost:8001/mcp?sessionId= 或 http://localhost:5173/agent/mcp?sessionId=
|
|
355
|
+
app.all('/mcp', express.json(), async (req: Request, res: Response) => {
|
|
356
|
+
console.log('AI Agent mcp auth:', req.user?.userId);
|
|
357
|
+
try {
|
|
358
|
+
if (req.query.sessionId) {
|
|
359
|
+
await handleStreamInspector(req, res);
|
|
360
|
+
} else {
|
|
361
|
+
await handleStreamRequest(req, res);
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error('AI Agent mcp error:', (error as Error).message);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// 用户提问端点,支持带 sessionId 的上下文调用
|
|
369
|
+
app.post('/question', express.json(), async (req: Request, res: Response) => {
|
|
370
|
+
try {
|
|
371
|
+
const sessionId = req.body.sessionId as string;
|
|
372
|
+
const client = clients[sessionId]?.client;
|
|
373
|
+
const question = req.body.question as string;
|
|
374
|
+
const answer = await ask(question, client); // 需自行实现 AI 调用 Client 的工具
|
|
375
|
+
res.json({ answer });
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.error('AI Agent question error:', (error as Error).message);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
app.listen(8001, () => {
|
|
382
|
+
console.log('AI Agent listening on port 8001');
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Vite 工程配置示例
|
|
387
|
+
|
|
388
|
+
以上示例代码均基于 Vite 工程配置运行,增加了环境变量以及代理转发等,以下是 `vite.config.ts` 示例:
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { defineConfig } from 'vite';
|
|
392
|
+
import vue from '@vitejs/plugin-vue';
|
|
393
|
+
import dotenv from 'dotenv';
|
|
394
|
+
import path from 'path';
|
|
395
|
+
|
|
396
|
+
// Vite 配置文件
|
|
397
|
+
export default defineConfig(({ mode }) => {
|
|
398
|
+
// 加载 .env 文件中的环境变量
|
|
399
|
+
dotenv.config({ path: '.env' });
|
|
400
|
+
|
|
401
|
+
const isProduction = mode === 'production';
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
// 注入环境变量到前端代码
|
|
405
|
+
define: {
|
|
406
|
+
'process.env': {
|
|
407
|
+
USER_TOKEN: process.env.USER_TOKEN,
|
|
408
|
+
ADMIN_TOKEN: process.env.ADMIN_TOKEN
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
plugins: [vue()],
|
|
412
|
+
server: {
|
|
413
|
+
proxy: {
|
|
414
|
+
// 代理 /agent 开头的请求到本地 8001 端口(AI Agent 服务)
|
|
415
|
+
'/agent': {
|
|
416
|
+
target: 'http://localhost:8001',
|
|
417
|
+
changeOrigin: true,
|
|
418
|
+
rewrite: (path) => path.replace(/^\/agent/, '')
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## 注意事项
|
|
427
|
+
|
|
428
|
+
1. 确保在服务端使用时安装 `express` 作为依赖
|
|
429
|
+
2. 使用 JWT 令牌认证时,请保护好密钥
|
|
430
|
+
3. 在生产环境中,应该设置适当的 CORS 配置
|
|
431
|
+
4. SSE 连接会保持打开状态,请确保适当处理资源释放
|
|
432
|
+
5. 使用 Streamable HTTP 时,记得在会话结束时调用 `terminateSession()` 方法关闭连接
|
|
433
|
+
|
|
434
|
+
## 许可证
|
|
435
|
+
|
|
436
|
+
MIT
|
package/client.d.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
|
|
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';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* MessageChannelTransport 是一个用于在浏览器上下文之间传输消息的类。
|
|
9
|
+
* 它使用 MessageChannel API 来实现不同上下文(如 iframe、worker、标签页、窗口等)之间的通信。
|
|
10
|
+
* 该类提供了初始化、发送消息、关闭通道等功能。
|
|
11
|
+
*/
|
|
12
|
+
declare class MessageChannelTransport {
|
|
13
|
+
protected _port: MessagePort | undefined;
|
|
14
|
+
protected _sessionId: string | undefined;
|
|
15
|
+
protected _started: boolean;
|
|
16
|
+
protected _closed: boolean;
|
|
17
|
+
onclose?: () => void;
|
|
18
|
+
onerror?: (error: Error) => void;
|
|
19
|
+
onmessage?: (message: JSONRPCMessage, extra?: {
|
|
20
|
+
authInfo?: AuthInfo;
|
|
21
|
+
}) => void;
|
|
22
|
+
/**
|
|
23
|
+
* 构造函数,初始化 MessageChannelTransport。
|
|
24
|
+
*
|
|
25
|
+
* @param port - 用于通信的 MessagePort。
|
|
26
|
+
* @param sessionId - 会话 ID。
|
|
27
|
+
*/
|
|
28
|
+
constructor(port?: MessagePort, sessionId?: string);
|
|
29
|
+
/**
|
|
30
|
+
* 初始化 MessageChannelTransport。
|
|
31
|
+
* 该方法会创建一个新的 MessageChannel,并将其端口传递给指定的全局对象。
|
|
32
|
+
* 如果传输已关闭,则抛出错误。
|
|
33
|
+
*/
|
|
34
|
+
start(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* 发送消息到 MessagePort。
|
|
37
|
+
* 如果传输已关闭,则抛出错误。
|
|
38
|
+
*
|
|
39
|
+
* @param message - 要发送的 JSON-RPC 消息。
|
|
40
|
+
*/
|
|
41
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* 关闭 MessageChannelTransport。
|
|
44
|
+
* 该方法会关闭 MessagePort,并触发关闭回调。
|
|
45
|
+
* 如果传输已关闭,则不执行任何操作。
|
|
46
|
+
*/
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* 获取当前会话的 sessionId。
|
|
50
|
+
*/
|
|
51
|
+
get sessionId(): string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 在浏览器端实现基于 MessageChannel API 的客户端传输类,
|
|
55
|
+
* 用于不同浏览器上下文(如 iframe、worker、标签页、窗口等)之间通信。
|
|
56
|
+
*/
|
|
57
|
+
declare class MessageChannelClientTransport extends MessageChannelTransport {
|
|
58
|
+
private _endpoint;
|
|
59
|
+
private _globalObject;
|
|
60
|
+
/**
|
|
61
|
+
* 创建一个监听通信的服务端实例。
|
|
62
|
+
*
|
|
63
|
+
* @param endpoint - 用于通信的端点。
|
|
64
|
+
* @param [globalObject] - 用于通信的全局对象(可选,默认当前全局对象)。
|
|
65
|
+
*/
|
|
66
|
+
constructor(endpoint: string, globalObject?: object);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 在浏览器端实现基于 MessageChannel API 的服务端传输类,
|
|
70
|
+
* 用于不同浏览器上下文(如 iframe、worker、标签页、窗口等)之间通信。
|
|
71
|
+
*/
|
|
72
|
+
declare class MessageChannelServerTransport extends MessageChannelTransport {
|
|
73
|
+
private _endpoint;
|
|
74
|
+
private _globalObject;
|
|
75
|
+
private _listen;
|
|
76
|
+
/**
|
|
77
|
+
* 创建一个监听通信的服务端实例。
|
|
78
|
+
*
|
|
79
|
+
* @param endpoint - 用于通信的端点。
|
|
80
|
+
* @param [globalObject] - 用于通信的全局对象(可选,默认当前全局对象)。
|
|
81
|
+
*/
|
|
82
|
+
constructor(endpoint: string, globalObject?: object);
|
|
83
|
+
/**
|
|
84
|
+
* 监听等待客户端连接。
|
|
85
|
+
* 该方法会返回一个 Promise,当客户端连接时解析。
|
|
86
|
+
*/
|
|
87
|
+
listen(): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 创建一对 MessageChannelTransport 实例,返回两个端口的传输实例。
|
|
91
|
+
* 这两个实例可以用于在不同的浏览器上下文之间进行通信。
|
|
92
|
+
*
|
|
93
|
+
* @returns 返回一对 MessageChannelTransport 实例。
|
|
94
|
+
*/
|
|
95
|
+
declare const createTransportPair: () => [MessageChannelTransport, MessageChannelTransport];
|
|
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;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 客户端代理选项接口
|
|
162
|
+
*/
|
|
163
|
+
interface ClientProxyOption {
|
|
164
|
+
/**
|
|
165
|
+
* MCP 客户端实例
|
|
166
|
+
*/
|
|
167
|
+
client: Client;
|
|
168
|
+
/**
|
|
169
|
+
* 代理服务器的 URL
|
|
170
|
+
*/
|
|
171
|
+
url: string;
|
|
172
|
+
/**
|
|
173
|
+
* 代理服务器的身份验证令牌
|
|
174
|
+
*/
|
|
175
|
+
token: string;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 创建一个 SSE 客户端代理
|
|
179
|
+
*
|
|
180
|
+
* @param options - 客户端代理选项
|
|
181
|
+
* @returns - 返回一个包含 transport 和 sessionId 的对象
|
|
182
|
+
*/
|
|
183
|
+
declare const createSseProxy: (option: ClientProxyOption) => Promise<{
|
|
184
|
+
transport: ProxySSEClientTransport;
|
|
185
|
+
sessionId: string;
|
|
186
|
+
}>;
|
|
187
|
+
/**
|
|
188
|
+
* 创建一个 Streamable HTTP 客户端代理
|
|
189
|
+
*
|
|
190
|
+
* @param options - 客户端代理选项
|
|
191
|
+
* @returns - 返回一个包含 transport 和 sessionId 的对象
|
|
192
|
+
*/
|
|
193
|
+
declare const createStreamProxy: (option: ClientProxyOption) => Promise<{
|
|
194
|
+
transport: ProxyStreamClientTransport;
|
|
195
|
+
sessionId: string;
|
|
196
|
+
}>;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 生成 SSEClientTransport 构造函数所需的第二个可选参数(传输配置)。
|
|
200
|
+
* 用于为 SSE 连接和 HTTP 请求统一设置认证头(Authorization)。
|
|
201
|
+
*
|
|
202
|
+
* @param token - 用于认证的 Bearer Token。
|
|
203
|
+
* @returns 返回传输配置对象,包含 requestInit 和 eventSourceInit 两部分:
|
|
204
|
+
* - requestInit:fetch 请求的初始化配置,自动带上认证头。
|
|
205
|
+
* - eventSourceInit:自定义 fetch 方法,确保 SSE 连接也带上认证头。
|
|
206
|
+
*/
|
|
207
|
+
declare const sseOptions: (token: string) => {
|
|
208
|
+
requestInit: {
|
|
209
|
+
headers: {
|
|
210
|
+
Authorization: string;
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
eventSourceInit: {
|
|
214
|
+
fetch: (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 生成 StreamableHTTPServerTransport 构造函数所需的第二个可选参数(传输配置)。
|
|
220
|
+
* 用于为 Streamable HTTP 连接和 HTTP 请求统一设置认证头(Authorization)。
|
|
221
|
+
*
|
|
222
|
+
* @param token - 用于认证的 Bearer Token。
|
|
223
|
+
* @param sessionId - 可选的 sessionId,用于标识会话。
|
|
224
|
+
* @returns 返回传输配置对象,包含以下内容:
|
|
225
|
+
* - requestInit:fetch 请求的初始化配置,自动带上认证头,并设置 Streamable HTTP 代理的会话 ID。
|
|
226
|
+
*/
|
|
227
|
+
declare const streamOptions: (token: string, sessionId?: `${string}-${string}-${string}-${string}-${string}`) => {
|
|
228
|
+
requestInit: {
|
|
229
|
+
headers: {
|
|
230
|
+
Authorization: string;
|
|
231
|
+
"stream-session-id": string;
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export { MessageChannelClientTransport, MessageChannelServerTransport, MessageChannelTransport, ProxySSEClientTransport, ProxyStreamClientTransport, createSseProxy, createStreamProxy, createTransportPair, sseOptions, streamOptions };
|
|
237
|
+
export type { ClientProxyOption };
|