@easbot/mcp 0.3.0 → 0.3.3
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.en.md +13 -8
- package/README.md +117 -112
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +51 -50
- package/dist/index.d.ts +51 -50
- package/dist/index.mjs +2 -2
- package/package.json +3 -3
package/README.en.md
CHANGED
|
@@ -21,10 +21,11 @@ MCP (Model Context Protocol) integration library providing complete MCP server a
|
|
|
21
21
|
### Create MCP Server
|
|
22
22
|
|
|
23
23
|
```typescript
|
|
24
|
-
import { HttpServerAdapter,
|
|
24
|
+
import { HttpServerAdapter, ToolRegistry } from '@easbot/mcp';
|
|
25
25
|
import { z } from 'zod';
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// Caller creates their own ToolRegistry (constructor-injected pattern)
|
|
28
|
+
const tools = new ToolRegistry();
|
|
28
29
|
tools.register({
|
|
29
30
|
name: 'my-tool',
|
|
30
31
|
description: 'A sample tool',
|
|
@@ -32,14 +33,14 @@ tools.register({
|
|
|
32
33
|
handler: async (args) => ({ content: [{ type: 'text', text: `Hello, ${args.name}!` }] })
|
|
33
34
|
});
|
|
34
35
|
|
|
35
|
-
// Create and start server
|
|
36
|
+
// Create and start server (inject tools)
|
|
36
37
|
const server = new HttpServerAdapter({
|
|
37
38
|
id: 'my-server',
|
|
38
39
|
name: 'My MCP Server',
|
|
39
40
|
version: '1.0.0',
|
|
40
41
|
transportType: 'http',
|
|
41
42
|
url: 'http://localhost:3000'
|
|
42
|
-
});
|
|
43
|
+
}, tools);
|
|
43
44
|
|
|
44
45
|
await server.start();
|
|
45
46
|
```
|
|
@@ -70,19 +71,23 @@ await client.disconnect();
|
|
|
70
71
|
### Create Servers from Configuration
|
|
71
72
|
|
|
72
73
|
```typescript
|
|
73
|
-
import { createServerFromConfig, createServersFromConfigMap } from '@easbot/mcp';
|
|
74
|
+
import { createServerFromConfig, createServersFromConfigMap, ToolRegistry } from '@easbot/mcp';
|
|
75
|
+
|
|
76
|
+
// Caller creates their own ToolRegistry
|
|
77
|
+
const tools = new ToolRegistry();
|
|
78
|
+
tools.register({ name: 'shared-tool', handler: ... });
|
|
74
79
|
|
|
75
80
|
// Create single server
|
|
76
81
|
const server = await createServerFromConfig('my-server', {
|
|
77
82
|
type: 'local',
|
|
78
83
|
command: ['node', 'server.js']
|
|
79
|
-
});
|
|
84
|
+
}, tools);
|
|
80
85
|
|
|
81
|
-
// Create multiple servers from config map
|
|
86
|
+
// Create multiple servers from config map (share same ToolRegistry)
|
|
82
87
|
const servers = await createServersFromConfigMap({
|
|
83
88
|
'server-1': { type: 'local', command: ['node', 'server1.js'] },
|
|
84
89
|
'server-2': { type: 'remote', url: 'https://mcp.example.com' }
|
|
85
|
-
});
|
|
90
|
+
}, tools);
|
|
86
91
|
```
|
|
87
92
|
|
|
88
93
|
## Development
|
package/README.md
CHANGED
|
@@ -1,112 +1,117 @@
|
|
|
1
|
-
[English](./README.en.md) | 中文
|
|
2
|
-
|
|
3
|
-
# @easbot/mcp
|
|
4
|
-
|
|
5
|
-
MCP (Model Context Protocol) 集成库,为 EASBot 生态系统提供完整的 MCP 服务器和客户端实现。
|
|
6
|
-
|
|
7
|
-
> 基于 @modelcontextprotocol/sdk 构建,提供 stdio 和 HTTP 两种传输方式,支持工具注册和动态管理。
|
|
8
|
-
|
|
9
|
-
## 特性
|
|
10
|
-
|
|
11
|
-
- **双传输支持**: 支持 stdio(本地进程)和 HTTP(远程服务)两种传输方式
|
|
12
|
-
- **服务器端实现**: 创建和管理 MCP 服务器,向客户端提供工具和资源
|
|
13
|
-
- **客户端实现**: 连接到 MCP 服务器,调用远程工具和读取资源
|
|
14
|
-
- **工具注册**: 全局工具注册表,支持动态注册/注销工具
|
|
15
|
-
- **OAuth 认证**: 支持 OAuth 2.0 认证,确保安全连接
|
|
16
|
-
- **配置驱动**: 支持从配置文件批量创建服务器实例
|
|
17
|
-
- **完整的类型定义**: 提供完整的 TypeScript 类型定义
|
|
18
|
-
|
|
19
|
-
## 快速开始
|
|
20
|
-
|
|
21
|
-
### 创建 MCP 服务器
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import { HttpServerAdapter,
|
|
25
|
-
import { z } from 'zod';
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
tools
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
1
|
+
[English](./README.en.md) | 中文
|
|
2
|
+
|
|
3
|
+
# @easbot/mcp
|
|
4
|
+
|
|
5
|
+
MCP (Model Context Protocol) 集成库,为 EASBot 生态系统提供完整的 MCP 服务器和客户端实现。
|
|
6
|
+
|
|
7
|
+
> 基于 @modelcontextprotocol/sdk 构建,提供 stdio 和 HTTP 两种传输方式,支持工具注册和动态管理。
|
|
8
|
+
|
|
9
|
+
## 特性
|
|
10
|
+
|
|
11
|
+
- **双传输支持**: 支持 stdio(本地进程)和 HTTP(远程服务)两种传输方式
|
|
12
|
+
- **服务器端实现**: 创建和管理 MCP 服务器,向客户端提供工具和资源
|
|
13
|
+
- **客户端实现**: 连接到 MCP 服务器,调用远程工具和读取资源
|
|
14
|
+
- **工具注册**: 全局工具注册表,支持动态注册/注销工具
|
|
15
|
+
- **OAuth 认证**: 支持 OAuth 2.0 认证,确保安全连接
|
|
16
|
+
- **配置驱动**: 支持从配置文件批量创建服务器实例
|
|
17
|
+
- **完整的类型定义**: 提供完整的 TypeScript 类型定义
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
### 创建 MCP 服务器
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { HttpServerAdapter, ToolRegistry } from '@easbot/mcp';
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
|
|
27
|
+
// 调用方自己 new ToolRegistry 并注册工具(构造注入模式)
|
|
28
|
+
const tools = new ToolRegistry();
|
|
29
|
+
tools.register({
|
|
30
|
+
name: 'my-tool',
|
|
31
|
+
description: '一个示例工具',
|
|
32
|
+
inputSchema: z.object({ name: z.string() }),
|
|
33
|
+
handler: async (args) => ({ content: [{ type: 'text', text: `Hello, ${args.name}!` }] })
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 创建并启动服务器(注入 tools)
|
|
37
|
+
const server = new HttpServerAdapter({
|
|
38
|
+
id: 'my-server',
|
|
39
|
+
name: 'My MCP Server',
|
|
40
|
+
version: '1.0.0',
|
|
41
|
+
transportType: 'http',
|
|
42
|
+
url: 'http://localhost:3000'
|
|
43
|
+
}, tools);
|
|
44
|
+
|
|
45
|
+
await server.start();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 连接到 MCP 服务器
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { MCPClient } from '@easbot/mcp';
|
|
52
|
+
|
|
53
|
+
const client = new MCPClient();
|
|
54
|
+
|
|
55
|
+
// 连接本地服务器
|
|
56
|
+
await client.connect('filesystem', {
|
|
57
|
+
type: 'local',
|
|
58
|
+
command: ['npx', '-y', '@modelcontextprotocol/server-filesystem', '/tmp']
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// 查看客户端信息
|
|
62
|
+
console.log(client.info);
|
|
63
|
+
|
|
64
|
+
// 调用工具
|
|
65
|
+
const result = await client.callTool('read_file', { path: '/tmp/test.txt' });
|
|
66
|
+
|
|
67
|
+
// 断开连接
|
|
68
|
+
await client.disconnect();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 从配置创建服务器
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { createServerFromConfig, createServersFromConfigMap, ToolRegistry } from '@easbot/mcp';
|
|
75
|
+
|
|
76
|
+
// 调用方自己 new ToolRegistry
|
|
77
|
+
const tools = new ToolRegistry();
|
|
78
|
+
tools.register({ name: 'shared-tool', handler: ... });
|
|
79
|
+
|
|
80
|
+
// 创建单个服务器
|
|
81
|
+
const server = await createServerFromConfig('my-server', {
|
|
82
|
+
type: 'local',
|
|
83
|
+
command: ['node', 'server.js']
|
|
84
|
+
}, tools);
|
|
85
|
+
|
|
86
|
+
// 从配置映射创建多个服务器(共享同一 ToolRegistry)
|
|
87
|
+
const servers = await createServersFromConfigMap({
|
|
88
|
+
'server-1': { type: 'local', command: ['node', 'server1.js'] },
|
|
89
|
+
'server-2': { type: 'remote', url: 'https://mcp.example.com' }
|
|
90
|
+
}, tools);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 开发
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# 安装依赖
|
|
97
|
+
pnpm install
|
|
98
|
+
|
|
99
|
+
# 构建
|
|
100
|
+
pnpm build
|
|
101
|
+
|
|
102
|
+
# 测试
|
|
103
|
+
pnpm test
|
|
104
|
+
|
|
105
|
+
# 类型检查
|
|
106
|
+
pnpm type-check
|
|
107
|
+
|
|
108
|
+
# 代码检查
|
|
109
|
+
pnpm lint
|
|
110
|
+
|
|
111
|
+
# 代码格式化
|
|
112
|
+
pnpm format
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 许可证
|
|
116
|
+
|
|
117
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
'use strict';var mcp_js=require('@modelcontextprotocol/sdk/server/mcp.js'),stdio_js=require('@modelcontextprotocol/sdk/server/stdio.js'),utils=require('@easbot/utils'),events=require('events'),d=require('zod/v4'),V=require('http'),streamableHttp_js=require('@modelcontextprotocol/sdk/server/streamableHttp.js'),index_js=require('@modelcontextprotocol/sdk/client/index.js'),stdio_js$1=require('@modelcontextprotocol/sdk/client/stdio.js'),streamableHttp_js$1=require('@modelcontextprotocol/sdk/client/streamableHttp.js'),sse_js=require('@modelcontextprotocol/sdk/client/sse.js'),types_js=require('@modelcontextprotocol/sdk/types.js'),auth_js=require('@modelcontextprotocol/sdk/client/auth.js'),v=require('zod');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var d__default=/*#__PURE__*/_interopDefault(d);var V__default=/*#__PURE__*/_interopDefault(V);var v__default=/*#__PURE__*/_interopDefault(v);var j=Object.defineProperty;var _=(s,t,e)=>t in s?j(s,t,{enumerable:true,configurable:true,writable:true,value:e}):s[t]=e;var a=(s,t,e)=>_(s,typeof t!="symbol"?t+"":t,e);var P={STDIO:"stdio",HTTP:"http"},l={STOPPED:"stopped",STARTING:"starting",RUNNING:"running",STOPPING:"stopping",ERROR:"error"};var L=utils.Log.create({service:"tool-registry"}),C={REGISTERED:"mcp.tool.registered",UNREGISTERED:"mcp.tool.unregistered",CLEARED:"mcp.tool.cleared"},M=class extends events.EventEmitter{constructor(){super();a(this,"tools",new Map);}register(e){if(!e.name||typeof e.name!="string")throw new Error("\u5DE5\u5177\u540D\u79F0\u65E0\u6548\uFF1A\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(this.tools.has(e.name)){L?.debug("tool already registered, skipping",{tool:e.name});return}this.tools.set(e.name,e),this.emit(C.REGISTERED,e);}unregister(e){return this.tools.has(e)?(this.tools.delete(e),this.emit(C.UNREGISTERED,e),true):false}get(e){return this.tools.get(e)}getAll(){return Array.from(this.tools.values())}getTools(){return Array.from(this.tools.values()).map(({handler:e,...r})=>r)}has(e){return this.tools.has(e)}getSize(){return this.tools.size}clear(){this.tools.clear(),this.emit(C.CLEARED);}},T=new M;var y=utils.Log.create({service:"mcp.server.stdio"}),b=class{constructor(t){a(this,"config");a(this,"server",null);a(this,"info");a(this,"isStarting",false);a(this,"isStopping",false);a(this,"toolEventCleanup",null);a(this,"handleToolRegistered",t=>{if(this.server){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),y.info("tool registered",{tool:t.name});}});this.config=t,this.info={id:t.id,name:t.name,version:t.version,transportType:t.transportType,status:l.STOPPED,createdAt:Date.now(),lastActivityAt:Date.now(),enabled:t.enabled};}getInfo(){return this.info}getConfig(){return this.config}createInstance(){let t=this;return {info:this.info,get server(){return t.server},start:()=>this.start(),stop:()=>this.stop(),restart:()=>this.restart()}}loadTools(){if(this.server)for(let t of T.getAll()){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),y.info("tool loaded",{tool:t.name});}}subscribeToolEvents(){this.toolEventCleanup=()=>{T.off(C.REGISTERED,this.handleToolRegistered);},T.on(C.REGISTERED,this.handleToolRegistered);}adaptToolHandler(t){return async(e,r)=>{let n={signal:r.signal,sessionId:r.sessionId,requestId:r.requestId,taskId:r.taskId,_meta:r._meta,authInfo:r.authInfo?{type:"bearer",claims:{clientId:r.authInfo.clientId,scopes:r.authInfo.scopes}}:void 0};return t(e,n)}}unsubscribeToolEvents(){this.toolEventCleanup&&(this.toolEventCleanup(),this.toolEventCleanup=null);}convertToZodSchema(t){if(t instanceof d__default.default.ZodType)return t;if(typeof t=="object"&&t!==null){let e=t;if(e.type==="object"){let r=e.properties||{},n=e.required||[],o={};for(let[i,m]of Object.entries(r))o[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!n.includes(i)).length===0)return d__default.default.object(o);let f={};for(let[i,m]of Object.entries(r)){let c=o[i];c&&(n.includes(i)?f[i]=c:f[i]=c.optional());}return d__default.default.object(f)}if(e.type==="string")return d__default.default.string();if(e.type==="number")return d__default.default.number();if(e.type==="boolean")return d__default.default.boolean();if(e.type==="array"){let r=e.items;return r?d__default.default.array(this.jsonSchemaToZod(r)):d__default.default.array(d__default.default.unknown())}}return d__default.default.unknown()}jsonSchemaToZod(t){switch(t.type){case "string":return d__default.default.string();case "number":return d__default.default.number();case "integer":return d__default.default.number().int();case "boolean":return d__default.default.boolean();case "null":return d__default.default.null();case "array":{let r=t.items;return r?d__default.default.array(this.jsonSchemaToZod(r)):d__default.default.array(d__default.default.unknown())}case "object":{let r=t.properties||{},n=t.required||[],o={};for(let[i,m]of Object.entries(r))o[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!n.includes(i)).length===0)return d__default.default.object(o);let f={};for(let[i,m]of Object.entries(r)){let c=o[i];c&&(n.includes(i)?f[i]=c:f[i]=c.optional());}return d__default.default.object(f)}default:return d__default.default.unknown()}}async start(){if(this.isStarting){y.warn("server is already starting",{id:this.info.id});return}if(this.info.status===l.RUNNING){y.debug("server is already running",{id:this.info.id});return}this.isStarting=true,this.info.status=l.STARTING;try{y.info("starting stdio mcp server",{id:this.info.id,name:this.info.name,command:this.config.command,args:this.config.args});let t=new stdio_js.StdioServerTransport;this.server=new mcp_js.McpServer({name:this.config.name,version:this.config.version}),this.loadTools(),this.subscribeToolEvents(),await this.server.connect(t),this.info.status=l.RUNNING,this.info.lastActivityAt=Date.now(),y.info("stdio mcp server started successfully",{id:this.info.id,name:this.info.name,toolCount:T.getSize()});}catch(t){throw this.info.status=l.ERROR,y.error("failed to start stdio mcp server",{id:this.info.id,error:t instanceof Error?t.message:String(t)}),t}finally{this.isStarting=false;}}async stop(){if(this.isStopping){y.warn("server is already stopping",{id:this.info.id});return}if(this.info.status!==l.RUNNING&&this.info.status!==l.ERROR){y.debug("server is not running",{id:this.info.id,status:this.info.status});return}this.isStopping=true,this.info.status=l.STOPPING;try{y.info("stopping stdio mcp server",{id:this.info.id,name:this.info.name}),this.unsubscribeToolEvents(),this.server&&(await this.server.close(),this.server=null),this.info.status=l.STOPPED,y.info("stdio mcp server stopped",{id:this.info.id,name:this.info.name});}catch(t){this.info.status=l.ERROR,y.error("error stopping stdio mcp server",{id:this.info.id,error:t instanceof Error?t.message:String(t)});}finally{this.isStopping=false;}}async restart(){y.info("restarting stdio mcp server",{id:this.info.id}),await this.stop(),await this.start();}};function q(s){return new b(s)}async function G(s){let t=new b(s);return await t.start(),t.createInstance()}var g=utils.Log.create({service:"mcp.server.http"}),R=class{constructor(t){a(this,"config");a(this,"server",null);a(this,"transport",null);a(this,"httpServer",null);a(this,"info");a(this,"isStarting",false);a(this,"isStopping",false);a(this,"toolCleanup",null);a(this,"handleTool",t=>{if(this.server){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),g.info("tool registered",{tool:t.name});}});this.config=t,this.info={id:t.id,name:t.name,version:t.version,transportType:t.transportType,status:l.STOPPED,createdAt:Date.now(),lastActivityAt:Date.now()};}getInfo(){return this.info}getConfig(){return this.config}createInstance(){let t=this;return {info:this.info,get server(){return t.server},start:()=>this.start(),stop:()=>this.stop(),restart:()=>this.restart()}}loadTools(){if(this.server)for(let t of T.getAll()){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),g.info("tool loaded",{tool:t.name});}}subscribeTools(){this.toolCleanup=()=>{T.off(C.REGISTERED,this.handleTool);},T.on(C.REGISTERED,this.handleTool);}convertToZodSchema(t){if(t instanceof d__default.default.ZodType)return t;if(typeof t=="object"&&t!==null){let e=t;if(e.type==="object"){let r=e.properties||{},n=e.required||[],o={};for(let[i,m]of Object.entries(r))o[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!n.includes(i)).length===0)return d__default.default.object(o);let f={};for(let[i,m]of Object.entries(r)){let c=o[i];c&&(n.includes(i)?f[i]=c:f[i]=c.optional());}return d__default.default.object(f)}if(e.type==="string")return d__default.default.string();if(e.type==="number")return d__default.default.number();if(e.type==="boolean")return d__default.default.boolean();if(e.type==="array"){let r=e.items;return r?d__default.default.array(this.jsonSchemaToZod(r)):d__default.default.array(d__default.default.unknown())}}return d__default.default.unknown()}jsonSchemaToZod(t){switch(t.type){case "string":return d__default.default.string();case "number":return d__default.default.number();case "integer":return d__default.default.number().int();case "boolean":return d__default.default.boolean();case "null":return d__default.default.null();case "array":{let r=t.items;return r?d__default.default.array(this.jsonSchemaToZod(r)):d__default.default.array(d__default.default.unknown())}case "object":{let r=t.properties||{},n=t.required||[],o={};for(let[i,m]of Object.entries(r))o[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!n.includes(i)).length===0)return d__default.default.object(o);let f={};for(let[i,m]of Object.entries(r)){let c=o[i];c&&(n.includes(i)?f[i]=c:f[i]=c.optional());}return d__default.default.object(f)}default:return d__default.default.unknown()}}adaptToolHandler(t){return async(e,r)=>{let n={signal:r.signal,sessionId:r.sessionId,requestId:r.requestId,taskId:r.taskId,_meta:r._meta,authInfo:r.authInfo?{type:"bearer",claims:{clientId:r.authInfo.clientId,scopes:r.authInfo.scopes}}:void 0};return t(e,n)}}unsubscribeTools(){this.toolCleanup&&(this.toolCleanup(),this.toolCleanup=null);}async start(){if(this.isStarting){g.warn("already starting",{id:this.info.id});return}if(this.info.status===l.RUNNING){g.debug("already running",{id:this.info.id});return}this.isStarting=true,this.info.status=l.STARTING;try{g.info("starting",{id:this.info.id,name:this.info.name,url:this.config.url});let t;try{t=new URL(this.config.url);}catch{throw new Error(`\u65E0\u6548\u7684 URL: ${this.config.url}`)}let e=parseInt(t.port||"3000",10),r=t.hostname||"0.0.0.0";if(Number.isNaN(e)||e<1||e>65535)throw new Error(`\u65E0\u6548\u7684\u7AEF\u53E3\u53F7: ${t.port}`);this.transport=new streamableHttp_js.StreamableHTTPServerTransport({sessionIdGenerator:()=>crypto.randomUUID()}),this.server=new mcp_js.McpServer({name:this.config.name,version:this.config.version}),this.loadTools(),this.subscribeTools(),await this.server.connect(this.transport),this.httpServer=V__default.default.createServer((n,o)=>{let h=n.headers.origin,f=(m,c,k)=>{o.writeHead(m,c),k?o.end(k):o.end();},i={"Access-Control-Allow-Origin":h||"*","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, mcpsessionid, mcp-protocol-version","Access-Control-Max-Age":"86400"};if(n.method==="OPTIONS"){f(204,i);return}if(n.method==="POST"){let m="";n.setEncoding("utf8"),n.on("data",c=>{m+=c;}),n.on("end",()=>{let c;if(m)try{c=JSON.parse(m);}catch{c=void 0;}this.transport&&this.transport.handleRequest(n,o,c);}),n.on("error",c=>{g.error("HTTP request error",{error:c.message}),f(500,i,JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal error"}}));});}else n.method==="GET"?this.transport&&this.transport.handleRequest(n,o,void 0):f(405,i,JSON.stringify({jsonrpc:"2.0",error:{code:-32601,message:"Method not found"}}));}),this.httpServer.on("error",n=>{g.error("HTTP server error",{error:n.message}),this.info.status=l.ERROR;}),this.httpServer.on("clientError",(n,o)=>{g.warn("HTTP client error",{error:n.message}),o.writable&&o.end(`HTTP/1.1 400 Bad Request\r
|
|
1
|
+
'use strict';var mcp_js=require('@modelcontextprotocol/sdk/server/mcp.js'),stdio_js=require('@modelcontextprotocol/sdk/server/stdio.js'),utils=require('@easbot/utils'),events=require('events'),d=require('zod'),G=require('http'),streamableHttp_js=require('@modelcontextprotocol/sdk/server/streamableHttp.js'),u=require('zod/v4'),index_js=require('@modelcontextprotocol/sdk/client/index.js'),stdio_js$1=require('@modelcontextprotocol/sdk/client/stdio.js'),streamableHttp_js$1=require('@modelcontextprotocol/sdk/client/streamableHttp.js'),sse_js=require('@modelcontextprotocol/sdk/client/sse.js'),types_js=require('@modelcontextprotocol/sdk/types.js'),auth_js=require('@modelcontextprotocol/sdk/client/auth.js');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var d__default=/*#__PURE__*/_interopDefault(d);var G__default=/*#__PURE__*/_interopDefault(G);var u__default=/*#__PURE__*/_interopDefault(u);var O=Object.defineProperty;var j=(s,t,e)=>t in s?O(s,t,{enumerable:true,configurable:true,writable:true,value:e}):s[t]=e;var a=(s,t,e)=>j(s,typeof t!="symbol"?t+"":t,e);var w={STDIO:"stdio",HTTP:"http"},l={STOPPED:"stopped",STARTING:"starting",RUNNING:"running",STOPPING:"stopping",ERROR:"error"};var D=utils.Log.create({service:"tool-registry"}),T={REGISTERED:"mcp.tool.registered",UNREGISTERED:"mcp.tool.unregistered",CLEARED:"mcp.tool.cleared"},M=class extends events.EventEmitter{constructor(){super();a(this,"tools",new Map);}register(e){if(!e.name||typeof e.name!="string")throw new Error("\u5DE5\u5177\u540D\u79F0\u65E0\u6548\uFF1A\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(this.tools.has(e.name)){D?.debug("tool already registered, skipping",{tool:e.name});return}this.tools.set(e.name,e),this.emit(T.REGISTERED,e);}unregister(e){return this.tools.has(e)?(this.tools.delete(e),this.emit(T.UNREGISTERED,e),true):false}get(e){return this.tools.get(e)}getAll(){return Array.from(this.tools.values())}getTools(){return Array.from(this.tools.values()).map(({handler:e,...r})=>r)}has(e){return this.tools.has(e)}getSize(){return this.tools.size}clear(){this.tools.clear(),this.emit(T.CLEARED);}};var y=utils.Log.create({service:"mcp.server.stdio"}),C=class{constructor(t,e){a(this,"config");a(this,"server",null);a(this,"info");a(this,"isStarting",false);a(this,"isStopping",false);a(this,"toolEventCleanup",null);a(this,"tools");a(this,"handleToolRegistered",t=>{if(this.server){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),y.info("tool registered",{tool:t.name});}});this.config=t,this.tools=e,this.info={id:t.id,name:t.name,version:t.version,transportType:t.transportType,status:l.STOPPED,createdAt:Date.now(),lastActivityAt:Date.now(),enabled:t.enabled};}getInfo(){return this.info}getConfig(){return this.config}createInstance(){let t=this;return {info:this.info,get server(){return t.server},start:()=>this.start(),stop:()=>this.stop(),restart:()=>this.restart()}}loadTools(){if(this.server)for(let t of this.tools.getAll()){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),y.info("tool loaded",{tool:t.name});}}subscribeToolEvents(){this.toolEventCleanup=()=>{this.tools.off(T.REGISTERED,this.handleToolRegistered);},this.tools.on(T.REGISTERED,this.handleToolRegistered);}adaptToolHandler(t){return async(e,r)=>{let o={signal:r.signal,sessionId:r.sessionId,requestId:r.requestId,taskId:r.taskId,_meta:r._meta,authInfo:r.authInfo?{type:"bearer",claims:{clientId:r.authInfo.clientId,scopes:r.authInfo.scopes}}:void 0};return t(e,o)}}unsubscribeToolEvents(){this.toolEventCleanup&&(this.toolEventCleanup(),this.toolEventCleanup=null);}convertToZodSchema(t){if(t instanceof d__default.default.ZodType)return t;if(typeof t=="object"&&t!==null){let e=t;if(e.type==="object"){let r=e.properties||{},o=e.required||[],n={};for(let[i,m]of Object.entries(r))n[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!o.includes(i)).length===0)return d__default.default.object(n);let f={};for(let[i,m]of Object.entries(r)){let c=n[i];c&&(o.includes(i)?f[i]=c:f[i]=c.optional());}return d__default.default.object(f)}if(e.type==="string")return d__default.default.string();if(e.type==="number")return d__default.default.number();if(e.type==="boolean")return d__default.default.boolean();if(e.type==="array"){let r=e.items;return r?d__default.default.array(this.jsonSchemaToZod(r)):d__default.default.array(d__default.default.unknown())}}return d__default.default.unknown()}jsonSchemaToZod(t){switch(t.type){case "string":return d__default.default.string();case "number":return d__default.default.number();case "integer":return d__default.default.number().int();case "boolean":return d__default.default.boolean();case "null":return d__default.default.null();case "array":{let r=t.items;return r?d__default.default.array(this.jsonSchemaToZod(r)):d__default.default.array(d__default.default.unknown())}case "object":{let r=t.properties||{},o=t.required||[],n={};for(let[i,m]of Object.entries(r))n[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!o.includes(i)).length===0)return d__default.default.object(n);let f={};for(let[i,m]of Object.entries(r)){let c=n[i];c&&(o.includes(i)?f[i]=c:f[i]=c.optional());}return d__default.default.object(f)}default:return d__default.default.unknown()}}async start(){if(this.isStarting){y.warn("server is already starting",{id:this.info.id});return}if(this.info.status===l.RUNNING){y.debug("server is already running",{id:this.info.id});return}this.isStarting=true,this.info.status=l.STARTING;try{y.info("starting stdio mcp server",{id:this.info.id,name:this.info.name,command:this.config.command,args:this.config.args});let t=new stdio_js.StdioServerTransport;this.server=new mcp_js.McpServer({name:this.config.name,version:this.config.version}),this.loadTools(),this.subscribeToolEvents(),await this.server.connect(t),this.info.status=l.RUNNING,this.info.lastActivityAt=Date.now(),y.info("stdio mcp server started successfully",{id:this.info.id,name:this.info.name,toolCount:this.tools.getSize()});}catch(t){throw this.info.status=l.ERROR,y.error("failed to start stdio mcp server",{id:this.info.id,error:t instanceof Error?t.message:String(t)}),t}finally{this.isStarting=false;}}async stop(){if(this.isStopping){y.warn("server is already stopping",{id:this.info.id});return}if(this.info.status!==l.RUNNING&&this.info.status!==l.ERROR){y.debug("server is not running",{id:this.info.id,status:this.info.status});return}this.isStopping=true,this.info.status=l.STOPPING;try{y.info("stopping stdio mcp server",{id:this.info.id,name:this.info.name}),this.unsubscribeToolEvents(),this.server&&(await this.server.close(),this.server=null),this.info.status=l.STOPPED,y.info("stdio mcp server stopped",{id:this.info.id,name:this.info.name});}catch(t){this.info.status=l.ERROR,y.error("error stopping stdio mcp server",{id:this.info.id,error:t instanceof Error?t.message:String(t)});}finally{this.isStopping=false;}}async restart(){y.info("restarting stdio mcp server",{id:this.info.id}),await this.stop(),await this.start();}};function z(s,t){return new C(s,t)}async function q(s,t){let e=new C(s,t);return await e.start(),e.createInstance()}var g=utils.Log.create({service:"mcp.server.http"}),R=class{constructor(t,e){a(this,"config");a(this,"server",null);a(this,"transport",null);a(this,"httpServer",null);a(this,"info");a(this,"isStarting",false);a(this,"isStopping",false);a(this,"toolCleanup",null);a(this,"tools");a(this,"handleTool",t=>{if(this.server){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),g.info("tool registered",{tool:t.name});}});this.config=t,this.tools=e,this.info={id:t.id,name:t.name,version:t.version,transportType:t.transportType,status:l.STOPPED,createdAt:Date.now(),lastActivityAt:Date.now()};}getInfo(){return this.info}getConfig(){return this.config}createInstance(){let t=this;return {info:this.info,get server(){return t.server},start:()=>this.start(),stop:()=>this.stop(),restart:()=>this.restart()}}loadTools(){if(this.server)for(let t of this.tools.getAll()){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),g.info("tool loaded",{tool:t.name});}}subscribeTools(){this.toolCleanup=()=>{this.tools.off(T.REGISTERED,this.handleTool);},this.tools.on(T.REGISTERED,this.handleTool);}convertToZodSchema(t){if(t instanceof u__default.default.ZodType)return t;if(typeof t=="object"&&t!==null){let e=t;if(e.type==="object"){let r=e.properties||{},o=e.required||[],n={};for(let[i,m]of Object.entries(r))n[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!o.includes(i)).length===0)return u__default.default.object(n);let f={};for(let[i,m]of Object.entries(r)){let c=n[i];c&&(o.includes(i)?f[i]=c:f[i]=c.optional());}return u__default.default.object(f)}if(e.type==="string")return u__default.default.string();if(e.type==="number")return u__default.default.number();if(e.type==="boolean")return u__default.default.boolean();if(e.type==="array"){let r=e.items;return r?u__default.default.array(this.jsonSchemaToZod(r)):u__default.default.array(u__default.default.unknown())}}return u__default.default.unknown()}jsonSchemaToZod(t){switch(t.type){case "string":return u__default.default.string();case "number":return u__default.default.number();case "integer":return u__default.default.number().int();case "boolean":return u__default.default.boolean();case "null":return u__default.default.null();case "array":{let r=t.items;return r?u__default.default.array(this.jsonSchemaToZod(r)):u__default.default.array(u__default.default.unknown())}case "object":{let r=t.properties||{},o=t.required||[],n={};for(let[i,m]of Object.entries(r))n[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!o.includes(i)).length===0)return u__default.default.object(n);let f={};for(let[i,m]of Object.entries(r)){let c=n[i];c&&(o.includes(i)?f[i]=c:f[i]=c.optional());}return u__default.default.object(f)}default:return u__default.default.unknown()}}adaptToolHandler(t){return async(e,r)=>{let o={signal:r.signal,sessionId:r.sessionId,requestId:r.requestId,taskId:r.taskId,_meta:r._meta,authInfo:r.authInfo?{type:"bearer",claims:{clientId:r.authInfo.clientId,scopes:r.authInfo.scopes}}:void 0};return t(e,o)}}unsubscribeTools(){this.toolCleanup&&(this.toolCleanup(),this.toolCleanup=null);}async start(){if(this.isStarting){g.warn("already starting",{id:this.info.id});return}if(this.info.status===l.RUNNING){g.debug("already running",{id:this.info.id});return}this.isStarting=true,this.info.status=l.STARTING;try{g.info("starting",{id:this.info.id,name:this.info.name,url:this.config.url});let t;try{t=new URL(this.config.url);}catch{throw new Error(`\u65E0\u6548\u7684 URL: ${this.config.url}`)}let e=parseInt(t.port||"3000",10),r=t.hostname||"0.0.0.0";if(Number.isNaN(e)||e<1||e>65535)throw new Error(`\u65E0\u6548\u7684\u7AEF\u53E3\u53F7: ${t.port}`);this.transport=new streamableHttp_js.StreamableHTTPServerTransport({sessionIdGenerator:()=>crypto.randomUUID()}),this.server=new mcp_js.McpServer({name:this.config.name,version:this.config.version}),this.loadTools(),this.subscribeTools(),await this.server.connect(this.transport),this.httpServer=G__default.default.createServer((o,n)=>{let h=o.headers.origin,f=(m,c,x)=>{n.writeHead(m,c),x?n.end(x):n.end();},i={"Access-Control-Allow-Origin":h||"*","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, mcpsessionid, mcp-protocol-version","Access-Control-Max-Age":"86400"};if(o.method==="OPTIONS"){f(204,i);return}if(o.method==="POST"){let m="";o.setEncoding("utf8"),o.on("data",c=>{m+=c;}),o.on("end",()=>{let c;if(m)try{c=JSON.parse(m);}catch{c=void 0;}this.transport&&this.transport.handleRequest(o,n,c);}),o.on("error",c=>{g.error("HTTP request error",{error:c.message}),f(500,i,JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal error"}}));});}else o.method==="GET"?this.transport&&this.transport.handleRequest(o,n,void 0):f(405,i,JSON.stringify({jsonrpc:"2.0",error:{code:-32601,message:"Method not found"}}));}),this.httpServer.on("error",o=>{g.error("HTTP server error",{error:o.message}),this.info.status=l.ERROR;}),this.httpServer.on("clientError",(o,n)=>{g.warn("HTTP client error",{error:o.message}),n.writable&&n.end(`HTTP/1.1 400 Bad Request\r
|
|
2
2
|
\r
|
|
3
|
-
`);}),this.httpServer.listen(e,r),g.info("HTTP server listening",{hostname:r,port:e}),this.info.status=l.RUNNING,this.info.lastActivityAt=Date.now(),g.info("started",{id:this.info.id,name:this.info.name,toolCount:T.getSize()});}catch(t){throw this.info.status=l.ERROR,g.error("failed to start",{id:this.info.id,error:t instanceof Error?t.message:String(t)}),t}finally{this.isStarting=false;}}async stop(){if(this.isStopping){g.warn("already stopping",{id:this.info.id});return}if(this.info.status!==l.RUNNING&&this.info.status!==l.ERROR){g.debug("not running",{id:this.info.id,status:this.info.status});return}this.isStopping=true,this.info.status=l.STOPPING;try{g.info("stopping",{id:this.info.id,name:this.info.name}),this.unsubscribeTools(),this.httpServer&&(await new Promise(t=>{this.httpServer.close(()=>t());}),this.httpServer=null),this.transport&&(await this.transport.close(),this.transport=null),this.server&&(await this.server.close(),this.server=null),this.info.status=l.STOPPED,g.info("stopped",{id:this.info.id});}catch(t){this.info.status=l.ERROR,g.error("failed to stop",{id:this.info.id,error:t instanceof Error?t.message:String(t)});}finally{this.isStopping=false;}}async restart(){g.info("restarting",{id:this.info.id}),await this.stop(),await this.start();}};function $(s){return new R(s)}async function J(s){let t=new R(s);return await t.start(),t.createInstance()}var A=utils.Log.create({service:"mcp.server.factory"});function Q(s){return s.type==="local"}function X(s){return s.type==="remote"}function Y(s,t){if(!t.command)throw new Error(`MCP server ${s} has no command specified`);return {id:s,name:s,version:"1.0.0",transportType:P.STDIO,enabled:t.enabled,timeout:t.timeout,command:t.command,args:(t?.args?.length??0)>0?t.args:void 0,env:t.env}}function tt(s,t){return {id:s,name:s,version:"1.0.0",transportType:P.HTTP,enabled:t.enabled,timeout:t.timeout,url:t.url,headers:t.headers}}async function et(s,t){if(A.debug("creating server from config",{name:s,type:t.type}),Q(t)){let e=Y(s,t),r=new b(e);return await r.start(),r.createInstance()}if(X(t)){let e=tt(s,t),r=new R(e);return await r.start(),r.createInstance()}throw new Error(`Unsupported MCP config type: ${t.type}`)}async function qt(s){let t={};for(let[e,r]of Object.entries(s))try{let n=await et(e,r);t[e]=n;}catch(n){A.error("failed to create server from config",{name:e,error:n instanceof Error?n.message:String(n)});}return t}var H={version:"0.3.0"};function I(){return H.version}var p=utils.Log.create({service:"mcp.client"}),E=6e4,x=class{constructor(){a(this,"id","");a(this,"client",null);a(this,"_info");a(this,"notificationCleanup",null);a(this,"pendingTransport",null);a(this,"oauthState","");a(this,"capturedRedirectUrl",null);a(this,"oauthConfig");a(this,"toolListChangedHandler",null);a(this,"oauthProvider",null);this._info=this.createInitialInfo();}get info(){return {...this._info}}get status(){return this._info.status}get tools(){return [...this._info.tools]}get resources(){return [...this._info.resources]}get prompts(){return [...this._info.prompts]}get clientId(){return this.id}setToolListChangedHandler(t){this.toolListChangedHandler=t;}setOAuthProvider(t){this.oauthProvider=t;}async connect(t,e){p.info("connecting to MCP server",{id:t,type:e.type}),await this.disconnect(),this.id=t,this._info=this.createInitialInfo(),this._info.id=t,this._info.name=t;try{if(e.type==="remote"&&(this.oauthConfig=e.oauth===!1?void 0:e.oauth??{}),this.client=await this.createClient(e),!this.client)return;this._info.status={status:"connected"},this._info.connectedAt=Date.now(),await this.fetchCapabilities(),p.info("connected to MCP server",{id:t,toolCount:this._info.tools.length});}catch(r){let n=r instanceof Error?r.message:String(r);this._info.status={status:"failed",error:n},this._info.error=n,p.error("failed to connect to MCP server",{id:t,error:n});}}async startAuth(){if(!this.pendingTransport)throw new Error("No pending OAuth flow. Please connect to a server that requires authentication first.");return this.oauthState=Array.from(crypto.getRandomValues(new Uint8Array(32))).map(t=>t.toString(16).padStart(2,"0")).join(""),this.oauthProvider&&await this.oauthProvider.saveState(this.oauthState),{authorizationUrl:this.capturedRedirectUrl?.toString()??"",state:this.oauthState}}async finishAuth(t){if(!this.pendingTransport)throw new Error("No pending OAuth flow.");try{await this.pendingTransport.finishAuth?.(t);}catch(e){throw p.error("failed to finish OAuth",{id:this.id,error:e}),e}this.pendingTransport=null,this.capturedRedirectUrl=null,p.info("OAuth completed, ready to reconnect",{id:this.id});}isAuthRequired(){return this._info.status.status==="needs_auth"}isClientRegistrationRequired(){return this._info.status.status==="needs_client_registration"}getPendingTransport(){return this.pendingTransport}getAuthorizationUrl(){return this.capturedRedirectUrl?.toString()??""}async disconnect(){this.notificationCleanup&&(this.notificationCleanup(),this.notificationCleanup=null),this.client&&(await this.client.close().catch(t=>{p.error("Failed to close MCP client",{id:this.id,error:t});}),this.client=null),this.pendingTransport=null,this.capturedRedirectUrl=null,this.toolListChangedHandler=null,this._info.status.status==="connected"&&(this._info.status={status:"disconnected"}),p.info("disconnected from MCP server",{id:this.id});}async listTools(){if(!this.client||this._info.status.status!=="connected")return {};let t={};for(let e of this._info.tools)t[e.name]={name:e.name,description:e.description,inputSchema:e.inputSchema};return t}async callTool(t,e,r){let n=Date.now();if(!this.client||this._info.status.status!=="connected")return {toolName:t,args:e,content:[],success:false,error:"Client not connected",duration:Date.now()-n};try{let o=await utils.withTimeout(this.client.callTool({name:t,arguments:e},types_js.CallToolResultSchema,{resetTimeoutOnProgress:!0,timeout:r}),r??E);return {toolName:t,args:e,content:o.content.map(h=>h.type==="text"?{type:"text",text:h.text}:h.type==="image"?{type:"image",data:h.data,mimeType:h.mimeType}:h.type==="resource"?{type:"resource",resource:h.resource}:{type:"text",text:String(h)}),success:!0,duration:Date.now()-n,metadata:o.metadata}}catch(o){return {toolName:t,args:e,content:[],success:false,error:o instanceof Error?o.message:String(o),duration:Date.now()-n}}}async listResources(){return [...this._info.resources]}async readResource(t){if(!this.client||this._info.status.status!=="connected"){p.warn("client not connected for readResource",{id:this.id});return}return this.client.readResource({uri:t}).catch(e=>{p.error("failed to read resource",{id:this.id,uri:t,error:e.message});})}async listPrompts(){return [...this._info.prompts]}async getPrompt(t,e){if(!this.client||this._info.status.status!=="connected"){p.warn("client not connected for getPrompt",{id:this.id});return}return this.client.getPrompt({name:t,arguments:e}).catch(r=>{p.error("failed to get prompt",{id:this.id,name:t,error:r.message});})}async refreshTools(){if(!(!this.client||this._info.status.status!=="connected"))try{let t=await utils.withTimeout(this.client.listTools(),E);this._info.tools=t.tools.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})),p.info("tools refreshed",{id:this.id,count:this._info.tools.length});}catch(t){p.error("failed to refresh tools",{id:this.id,error:t instanceof Error?t.message:String(t)});}}async createClient(t){return t.type==="local"?await this.createLocalClient(t):t.type==="remote"?await this.createRemoteClient(t):null}async fetchCapabilities(){if(this.client){try{let t=await utils.withTimeout(this.client.listTools(),E);this._info.tools=t.tools.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}));}catch(t){p.debug("failed to get tools",{id:this.id,error:t instanceof Error?t.message:String(t)});}try{let t=await utils.withTimeout(this.client.listResources(),5e3);this._info.resources=t.resources.map(e=>({name:e.name,uri:e.uri,description:e.description,mimeType:e.mimeType}));}catch{p.debug("failed to get resources",{id:this.id});}try{let t=await utils.withTimeout(this.client.listPrompts(),5e3);this._info.prompts=t.prompts.map(e=>({name:e.name,description:e.description,arguments:e.arguments?.map(r=>({name:r.name,description:r.description,required:r.required}))}));}catch{p.debug("failed to get prompts",{id:this.id});}}}async createLocalClient(t){let[e,...r]=t.command;if(!e)return this._info.status={status:"failed",error:"Command is empty"},null;let n=new stdio_js$1.StdioClientTransport({stderr:"pipe",command:e,args:r.length>0?r:void 0,cwd:t.cwd??process.cwd(),env:Object.fromEntries(Object.entries({...process.env,...t.environment}).filter(([,o])=>o!==void 0))});n.stderr?.on("data",o=>{p.info(`mcp stderr: ${o.toString()}`,{id:this.id});});try{let o=new index_js.Client({name:"easbot-mcp",version:I()});return await utils.withTimeout(o.connect(n),t.timeout??E),this.registerNotificationHandler(o),o}catch(o){let h=o instanceof Error?o.message:String(o);return this._info.status={status:"failed",error:h},p.error("local mcp startup failed",{id:this.id,command:[e,...r],error:h}),null}}async createRemoteClient(t){let e={headers:t.headers};if((t.transport||"streamable-http")==="sse"){p.info("using SSE transport",{id:this.id,url:t.url});let n=new sse_js.SSEClientTransport(new URL(t.url),{requestInit:e});return await this.connectWithTransport(n,t)}else {p.info("using StreamableHTTP transport",{id:this.id,url:t.url});let n=new streamableHttp_js$1.StreamableHTTPClientTransport(new URL(t.url),{requestInit:e});return await this.connectWithTransport(n,t)}}async connectWithTransport(t,e){try{let r=new index_js.Client({name:"easbot-mcp",version:I()});return await r.connect(t),this.registerNotificationHandler(r),r}catch(r){return this.handleConnectionError(r,t,e),null}}handleConnectionError(t,e,r){if(t instanceof auth_js.UnauthorizedError){let n=t instanceof Error?t.message:String(t);n.includes("registration")||n.includes("client_id")?this._info.status={status:"needs_client_registration",error:"Server does not support dynamic client registration. Please provide clientId in config."}:(this._info.status={status:"needs_auth"},this.pendingTransport=e,this.capturedRedirectUrl=this.extractRedirectUrl(t));}else {let n=t instanceof Error?t.message:String(t);this._info.status={status:"failed",error:n};}p.error("remote mcp connection failed",{id:this.id,url:r.url,status:this._info.status});}extractRedirectUrl(t){let r=t.message.match(/https?:\/\/[^\s]+/);if(r)try{return new URL(r[0])}catch{return null}return null}registerNotificationHandler(t){let e=async()=>{p.info("tools list changed notification received",{id:this.id}),await this.refreshTools(),this.toolListChangedHandler&&await this.toolListChangedHandler();};t.setNotificationHandler(types_js.ToolListChangedNotificationSchema,e),this.notificationCleanup=()=>{t.removeNotificationHandler("notifications/tools/list_changed");};}createInitialInfo(){return {id:"",name:"",status:{status:"disconnected"},tools:[],resources:[],prompts:[]}}};function pt(){return new x}var he=v__default.default.discriminatedUnion("status",[v__default.default.object({status:v__default.default.literal("connected")}).meta({ref:"MCPStatusConnected"}),v__default.default.object({status:v__default.default.literal("disconnected")}).meta({ref:"MCPStatusDisconnected"}),v__default.default.object({status:v__default.default.literal("failed"),error:v__default.default.string()}).meta({ref:"MCPStatusFailed"}),v__default.default.object({status:v__default.default.literal("needs_auth")}).meta({ref:"MCPStatusNeedsAuth"}),v__default.default.object({status:v__default.default.literal("needs_client_registration"),error:v__default.default.string()}).meta({ref:"MCPStatusNeedsClientRegistration"}),v__default.default.object({status:v__default.default.literal("disabled")}).meta({ref:"MCPStatusDisabled"})]);var Ce="@easbot/mcp";exports.HttpServerAdapter=R;exports.MCPClient=x;exports.NAME=Ce;exports.ServerStatus=l;exports.ServerTransportType=P;exports.Status=he;exports.StdioServerAdapter=b;exports.ToolRegistry=M;exports.ToolRegistryEvent=C;exports.createAndStartHttpServer=J;exports.createAndStartStdioServer=G;exports.createHttpServer=$;exports.createMCPClient=pt;exports.createServerFromConfig=et;exports.createServersFromConfigMap=qt;exports.createStdioServer=q;exports.getVersion=I;exports.tools=T;
|
|
3
|
+
`);}),this.httpServer.listen(e,r),g.info("HTTP server listening",{hostname:r,port:e}),this.info.status=l.RUNNING,this.info.lastActivityAt=Date.now(),g.info("started",{id:this.info.id,name:this.info.name,toolCount:this.tools.getSize()});}catch(t){throw this.info.status=l.ERROR,g.error("failed to start",{id:this.info.id,error:t instanceof Error?t.message:String(t)}),t}finally{this.isStarting=false;}}async stop(){if(this.isStopping){g.warn("already stopping",{id:this.info.id});return}if(this.info.status!==l.RUNNING&&this.info.status!==l.ERROR){g.debug("not running",{id:this.info.id,status:this.info.status});return}this.isStopping=true,this.info.status=l.STOPPING;try{g.info("stopping",{id:this.info.id,name:this.info.name}),this.unsubscribeTools(),this.httpServer&&(await new Promise(t=>{this.httpServer.close(()=>t());}),this.httpServer=null),this.transport&&(await this.transport.close(),this.transport=null),this.server&&(await this.server.close(),this.server=null),this.info.status=l.STOPPED,g.info("stopped",{id:this.info.id});}catch(t){this.info.status=l.ERROR,g.error("failed to stop",{id:this.info.id,error:t instanceof Error?t.message:String(t)});}finally{this.isStopping=false;}}async restart(){g.info("restarting",{id:this.info.id}),await this.stop(),await this.start();}};function B(s,t){return new R(s,t)}async function $(s,t){let e=new R(s,t);return await e.start(),e.createInstance()}var k=utils.Log.create({service:"mcp.server.factory"});function W(s){return s.type==="local"}function Q(s){return s.type==="remote"}function X(s,t){if(!t.command)throw new Error(`MCP server ${s} has no command specified`);return {id:s,name:s,version:"1.0.0",transportType:w.STDIO,enabled:t.enabled,timeout:t.timeout,command:t.command,args:(t?.args?.length??0)>0?t.args:void 0,env:t.env}}function Y(s,t){return {id:s,name:s,version:"1.0.0",transportType:w.HTTP,enabled:t.enabled,timeout:t.timeout,url:t.url,headers:t.headers}}async function tt(s,t,e){if(k.debug("creating server from config",{name:s,type:t.type}),W(t)){let r=X(s,t),o=new C(r,e);return await o.start(),o.createInstance()}if(Q(t)){let r=Y(s,t),o=new R(r,e);return await o.start(),o.createInstance()}throw new Error(`Unsupported MCP config type: ${t.type}`)}async function Kt(s,t){let e={};for(let[r,o]of Object.entries(s))try{let n=await tt(r,o,t);e[r]=n;}catch(n){k.error("failed to create server from config",{name:r,error:n instanceof Error?n.message:String(n)});}return e}var A={version:"0.3.3"};function P(){return A.version}var p=utils.Log.create({service:"mcp.client"}),I=6e4,E=class{constructor(){a(this,"id","");a(this,"client",null);a(this,"_info");a(this,"notificationCleanup",null);a(this,"pendingTransport",null);a(this,"oauthState","");a(this,"capturedRedirectUrl",null);a(this,"oauthConfig");a(this,"toolListChangedHandler",null);a(this,"oauthProvider",null);this._info=this.createInitialInfo();}get info(){return {...this._info}}get status(){return this._info.status}get tools(){return [...this._info.tools]}get resources(){return [...this._info.resources]}get prompts(){return [...this._info.prompts]}get clientId(){return this.id}setToolListChangedHandler(t){this.toolListChangedHandler=t;}setOAuthProvider(t){this.oauthProvider=t;}async connect(t,e){p.info("connecting to MCP server",{id:t,type:e.type}),await this.disconnect(),this.id=t,this._info=this.createInitialInfo(),this._info.id=t,this._info.name=t;try{if(e.type==="remote"&&(this.oauthConfig=e.oauth===!1?void 0:e.oauth??{}),this.client=await this.createClient(e),!this.client)return;this._info.status={status:"connected"},this._info.connectedAt=Date.now(),await this.fetchCapabilities(),p.info("connected to MCP server",{id:t,toolCount:this._info.tools.length});}catch(r){let o=r instanceof Error?r.message:String(r);this._info.status={status:"failed",error:o},this._info.error=o,p.error("failed to connect to MCP server",{id:t,error:o});}}async startAuth(){if(!this.pendingTransport)throw new Error("No pending OAuth flow. Please connect to a server that requires authentication first.");return this.oauthState=Array.from(crypto.getRandomValues(new Uint8Array(32))).map(t=>t.toString(16).padStart(2,"0")).join(""),this.oauthProvider&&await this.oauthProvider.saveState(this.oauthState),{authorizationUrl:this.capturedRedirectUrl?.toString()??"",state:this.oauthState}}async finishAuth(t){if(!this.pendingTransport)throw new Error("No pending OAuth flow.");try{await this.pendingTransport.finishAuth?.(t);}catch(e){throw p.error("failed to finish OAuth",{id:this.id,error:e}),e}this.pendingTransport=null,this.capturedRedirectUrl=null,p.info("OAuth completed, ready to reconnect",{id:this.id});}isAuthRequired(){return this._info.status.status==="needs_auth"}isClientRegistrationRequired(){return this._info.status.status==="needs_client_registration"}getPendingTransport(){return this.pendingTransport}getAuthorizationUrl(){return this.capturedRedirectUrl?.toString()??""}async disconnect(){this.notificationCleanup&&(this.notificationCleanup(),this.notificationCleanup=null),this.client&&(await this.client.close().catch(t=>{p.error("Failed to close MCP client",{id:this.id,error:t});}),this.client=null),this.pendingTransport=null,this.capturedRedirectUrl=null,this.toolListChangedHandler=null,this._info.status.status==="connected"&&(this._info.status={status:"disconnected"}),p.info("disconnected from MCP server",{id:this.id});}async listTools(){if(!this.client||this._info.status.status!=="connected")return {};let t={};for(let e of this._info.tools)t[e.name]={name:e.name,description:e.description,inputSchema:e.inputSchema};return t}async callTool(t,e,r){let o=Date.now();if(!this.client||this._info.status.status!=="connected")return {toolName:t,args:e,content:[],success:false,error:"Client not connected",duration:Date.now()-o};try{let n=await utils.withTimeout(this.client.callTool({name:t,arguments:e},types_js.CallToolResultSchema,{resetTimeoutOnProgress:!0,timeout:r}),r??I);return {toolName:t,args:e,content:n.content.map(h=>h.type==="text"?{type:"text",text:h.text}:h.type==="image"?{type:"image",data:h.data,mimeType:h.mimeType}:h.type==="resource"?{type:"resource",resource:h.resource}:{type:"text",text:String(h)}),success:!0,duration:Date.now()-o,metadata:n.metadata}}catch(n){return {toolName:t,args:e,content:[],success:false,error:n instanceof Error?n.message:String(n),duration:Date.now()-o}}}async listResources(){return [...this._info.resources]}async readResource(t){if(!this.client||this._info.status.status!=="connected"){p.warn("client not connected for readResource",{id:this.id});return}return this.client.readResource({uri:t}).catch(e=>{p.error("failed to read resource",{id:this.id,uri:t,error:e.message});})}async listPrompts(){return [...this._info.prompts]}async getPrompt(t,e){if(!this.client||this._info.status.status!=="connected"){p.warn("client not connected for getPrompt",{id:this.id});return}return this.client.getPrompt({name:t,arguments:e}).catch(r=>{p.error("failed to get prompt",{id:this.id,name:t,error:r.message});})}async refreshTools(){if(!(!this.client||this._info.status.status!=="connected"))try{let t=await utils.withTimeout(this.client.listTools(),I);this._info.tools=t.tools.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})),p.info("tools refreshed",{id:this.id,count:this._info.tools.length});}catch(t){p.error("failed to refresh tools",{id:this.id,error:t instanceof Error?t.message:String(t)});}}async createClient(t){return t.type==="local"?await this.createLocalClient(t):t.type==="remote"?await this.createRemoteClient(t):null}async fetchCapabilities(){if(this.client){try{let t=await utils.withTimeout(this.client.listTools(),I);this._info.tools=t.tools.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}));}catch(t){p.debug("failed to get tools",{id:this.id,error:t instanceof Error?t.message:String(t)});}try{let t=await utils.withTimeout(this.client.listResources(),5e3);this._info.resources=t.resources.map(e=>({name:e.name,uri:e.uri,description:e.description,mimeType:e.mimeType}));}catch{p.debug("failed to get resources",{id:this.id});}try{let t=await utils.withTimeout(this.client.listPrompts(),5e3);this._info.prompts=t.prompts.map(e=>({name:e.name,description:e.description,arguments:e.arguments?.map(r=>({name:r.name,description:r.description,required:r.required}))}));}catch{p.debug("failed to get prompts",{id:this.id});}}}async createLocalClient(t){let[e,...r]=t.command;if(!e)return this._info.status={status:"failed",error:"Command is empty"},null;let o=new stdio_js$1.StdioClientTransport({stderr:"pipe",command:e,args:r.length>0?r:void 0,cwd:t.cwd??process.cwd(),env:Object.fromEntries(Object.entries({...process.env,...t.environment}).filter(([,n])=>n!==void 0))});o.stderr?.on("data",n=>{p.info(`mcp stderr: ${n.toString()}`,{id:this.id});});try{let n=new index_js.Client({name:"easbot-mcp",version:P()});return await utils.withTimeout(n.connect(o),t.timeout??I),this.registerNotificationHandler(n),n}catch(n){let h=n instanceof Error?n.message:String(n);return this._info.status={status:"failed",error:h},p.error("local mcp startup failed",{id:this.id,command:[e,...r],error:h}),null}}async createRemoteClient(t){let e={headers:t.headers};if((t.transport||"streamable-http")==="sse"){p.info("using SSE transport",{id:this.id,url:t.url});let o=new sse_js.SSEClientTransport(new URL(t.url),{requestInit:e});return await this.connectWithTransport(o,t)}else {p.info("using StreamableHTTP transport",{id:this.id,url:t.url});let o=new streamableHttp_js$1.StreamableHTTPClientTransport(new URL(t.url),{requestInit:e});return await this.connectWithTransport(o,t)}}async connectWithTransport(t,e){try{let r=new index_js.Client({name:"easbot-mcp",version:P()});return await r.connect(t),this.registerNotificationHandler(r),r}catch(r){return this.handleConnectionError(r,t,e),null}}handleConnectionError(t,e,r){if(t instanceof auth_js.UnauthorizedError){let o=t instanceof Error?t.message:String(t);o.includes("registration")||o.includes("client_id")?this._info.status={status:"needs_client_registration",error:"Server does not support dynamic client registration. Please provide clientId in config."}:(this._info.status={status:"needs_auth"},this.pendingTransport=e,this.capturedRedirectUrl=this.extractRedirectUrl(t));}else {let o=t instanceof Error?t.message:String(t);this._info.status={status:"failed",error:o};}p.error("remote mcp connection failed",{id:this.id,url:r.url,status:this._info.status});}extractRedirectUrl(t){let r=t.message.match(/https?:\/\/[^\s]+/);if(r)try{return new URL(r[0])}catch{return null}return null}registerNotificationHandler(t){let e=async()=>{p.info("tools list changed notification received",{id:this.id}),await this.refreshTools(),this.toolListChangedHandler&&await this.toolListChangedHandler();};t.setNotificationHandler(types_js.ToolListChangedNotificationSchema,e),this.notificationCleanup=()=>{t.removeNotificationHandler("notifications/tools/list_changed");};}createInitialInfo(){return {id:"",name:"",status:{status:"disconnected"},tools:[],resources:[],prompts:[]}}};function lt(){return new E}var ve=d__default.default.discriminatedUnion("status",[d__default.default.object({status:d__default.default.literal("connected")}).meta({ref:"MCPStatusConnected"}),d__default.default.object({status:d__default.default.literal("disconnected")}).meta({ref:"MCPStatusDisconnected"}),d__default.default.object({status:d__default.default.literal("failed"),error:d__default.default.string()}).meta({ref:"MCPStatusFailed"}),d__default.default.object({status:d__default.default.literal("needs_auth")}).meta({ref:"MCPStatusNeedsAuth"}),d__default.default.object({status:d__default.default.literal("needs_client_registration"),error:d__default.default.string()}).meta({ref:"MCPStatusNeedsClientRegistration"}),d__default.default.object({status:d__default.default.literal("disabled")}).meta({ref:"MCPStatusDisabled"})]);var we="@easbot/mcp";exports.HttpServerAdapter=R;exports.MCPClient=E;exports.NAME=we;exports.ServerStatus=l;exports.ServerTransportType=w;exports.Status=ve;exports.StdioServerAdapter=C;exports.ToolRegistry=M;exports.ToolRegistryEvent=T;exports.createAndStartHttpServer=$;exports.createAndStartStdioServer=q;exports.createHttpServer=B;exports.createMCPClient=lt;exports.createServerFromConfig=tt;exports.createServersFromConfigMap=Kt;exports.createStdioServer=z;exports.getVersion=P;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
4
|
-
import z, { ZodType } from 'zod';
|
|
5
2
|
import { EventEmitter } from 'node:events';
|
|
6
3
|
import { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import z, { ZodType } from 'zod';
|
|
5
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
6
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
7
7
|
|
|
8
8
|
interface McpLocalConfig {
|
|
9
9
|
type: 'local';
|
|
@@ -82,6 +82,43 @@ interface ServerInstance$2 {
|
|
|
82
82
|
restart(): Promise<void>;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
type ToolInputSchema = ZodType | Record<string, unknown>;
|
|
86
|
+
type RequestId = string | number;
|
|
87
|
+
interface ToolContext {
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
sessionId?: string;
|
|
90
|
+
requestId: RequestId;
|
|
91
|
+
taskId?: string;
|
|
92
|
+
_meta?: Record<string, unknown>;
|
|
93
|
+
authInfo?: {
|
|
94
|
+
type: string;
|
|
95
|
+
claims?: Record<string, unknown>;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
type ToolHandler = (args: unknown, ctx: ToolContext) => Promise<CallToolResult>;
|
|
99
|
+
interface ToolDefinition extends Omit<Tool, 'inputSchema'> {
|
|
100
|
+
inputSchema?: ToolInputSchema;
|
|
101
|
+
handler: ToolHandler;
|
|
102
|
+
}
|
|
103
|
+
declare const ToolRegistryEvent: {
|
|
104
|
+
readonly REGISTERED: "mcp.tool.registered";
|
|
105
|
+
readonly UNREGISTERED: "mcp.tool.unregistered";
|
|
106
|
+
readonly CLEARED: "mcp.tool.cleared";
|
|
107
|
+
};
|
|
108
|
+
type ToolRegistryEvent = (typeof ToolRegistryEvent)[keyof typeof ToolRegistryEvent];
|
|
109
|
+
declare class ToolRegistry extends EventEmitter {
|
|
110
|
+
private tools;
|
|
111
|
+
constructor();
|
|
112
|
+
register(tool: ToolDefinition): void;
|
|
113
|
+
unregister(name: string): boolean;
|
|
114
|
+
get(name: string): ToolDefinition | undefined;
|
|
115
|
+
getAll(): ToolDefinition[];
|
|
116
|
+
getTools(): Omit<ToolDefinition, 'handler'>[];
|
|
117
|
+
has(name: string): boolean;
|
|
118
|
+
getSize(): number;
|
|
119
|
+
clear(): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
85
122
|
interface ServerInstance$1 {
|
|
86
123
|
info: ServerInfo;
|
|
87
124
|
get server(): McpServer | null;
|
|
@@ -96,7 +133,8 @@ declare class StdioServerAdapter {
|
|
|
96
133
|
private isStarting;
|
|
97
134
|
private isStopping;
|
|
98
135
|
private toolEventCleanup;
|
|
99
|
-
|
|
136
|
+
private readonly tools;
|
|
137
|
+
constructor(config: StdioServerConfig, tools: ToolRegistry);
|
|
100
138
|
getInfo(): ServerInfo;
|
|
101
139
|
getConfig(): StdioServerConfig;
|
|
102
140
|
createInstance(): ServerInstance$1;
|
|
@@ -111,8 +149,8 @@ declare class StdioServerAdapter {
|
|
|
111
149
|
stop(): Promise<void>;
|
|
112
150
|
restart(): Promise<void>;
|
|
113
151
|
}
|
|
114
|
-
declare function createStdioServer(config: StdioServerConfig): StdioServerAdapter;
|
|
115
|
-
declare function createAndStartStdioServer(config: StdioServerConfig): Promise<ServerInstance$1>;
|
|
152
|
+
declare function createStdioServer(config: StdioServerConfig, tools: ToolRegistry): StdioServerAdapter;
|
|
153
|
+
declare function createAndStartStdioServer(config: StdioServerConfig, tools: ToolRegistry): Promise<ServerInstance$1>;
|
|
116
154
|
|
|
117
155
|
interface ServerInstance {
|
|
118
156
|
info: ServerInfo;
|
|
@@ -130,7 +168,8 @@ declare class HttpServerAdapter {
|
|
|
130
168
|
private isStarting;
|
|
131
169
|
private isStopping;
|
|
132
170
|
private toolCleanup;
|
|
133
|
-
|
|
171
|
+
private readonly tools;
|
|
172
|
+
constructor(config: HttpServerConfig, tools: ToolRegistry);
|
|
134
173
|
getInfo(): ServerInfo;
|
|
135
174
|
getConfig(): HttpServerConfig;
|
|
136
175
|
createInstance(): ServerInstance;
|
|
@@ -145,11 +184,11 @@ declare class HttpServerAdapter {
|
|
|
145
184
|
stop(): Promise<void>;
|
|
146
185
|
restart(): Promise<void>;
|
|
147
186
|
}
|
|
148
|
-
declare function createHttpServer(config: HttpServerConfig): HttpServerAdapter;
|
|
149
|
-
declare function createAndStartHttpServer(config: HttpServerConfig): Promise<ServerInstance>;
|
|
187
|
+
declare function createHttpServer(config: HttpServerConfig, tools: ToolRegistry): HttpServerAdapter;
|
|
188
|
+
declare function createAndStartHttpServer(config: HttpServerConfig, tools: ToolRegistry): Promise<ServerInstance>;
|
|
150
189
|
|
|
151
|
-
declare function createServerFromConfig(name: string, config: McpConfig): Promise<ServerInstance$2>;
|
|
152
|
-
declare function createServersFromConfigMap(configs: Record<string, McpConfig
|
|
190
|
+
declare function createServerFromConfig(name: string, config: McpConfig, tools: ToolRegistry): Promise<ServerInstance$2>;
|
|
191
|
+
declare function createServersFromConfigMap(configs: Record<string, McpConfig>, tools: ToolRegistry): Promise<Record<string, ServerInstance$2>>;
|
|
153
192
|
|
|
154
193
|
interface OAuthConfig {
|
|
155
194
|
clientId?: string;
|
|
@@ -428,46 +467,8 @@ declare class MCPClient {
|
|
|
428
467
|
}
|
|
429
468
|
declare function createMCPClient(): MCPClient;
|
|
430
469
|
|
|
431
|
-
type ToolInputSchema = ZodType | Record<string, unknown>;
|
|
432
|
-
type RequestId = string | number;
|
|
433
|
-
interface ToolContext {
|
|
434
|
-
signal?: AbortSignal;
|
|
435
|
-
sessionId?: string;
|
|
436
|
-
requestId: RequestId;
|
|
437
|
-
taskId?: string;
|
|
438
|
-
_meta?: Record<string, unknown>;
|
|
439
|
-
authInfo?: {
|
|
440
|
-
type: string;
|
|
441
|
-
claims?: Record<string, unknown>;
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
type ToolHandler = (args: unknown, ctx: ToolContext) => Promise<CallToolResult>;
|
|
445
|
-
interface ToolDefinition extends Omit<Tool, 'inputSchema'> {
|
|
446
|
-
inputSchema?: ToolInputSchema;
|
|
447
|
-
handler: ToolHandler;
|
|
448
|
-
}
|
|
449
|
-
declare const ToolRegistryEvent: {
|
|
450
|
-
readonly REGISTERED: "mcp.tool.registered";
|
|
451
|
-
readonly UNREGISTERED: "mcp.tool.unregistered";
|
|
452
|
-
readonly CLEARED: "mcp.tool.cleared";
|
|
453
|
-
};
|
|
454
|
-
type ToolRegistryEvent = (typeof ToolRegistryEvent)[keyof typeof ToolRegistryEvent];
|
|
455
|
-
declare class ToolRegistry extends EventEmitter {
|
|
456
|
-
private tools;
|
|
457
|
-
constructor();
|
|
458
|
-
register(tool: ToolDefinition): void;
|
|
459
|
-
unregister(name: string): boolean;
|
|
460
|
-
get(name: string): ToolDefinition | undefined;
|
|
461
|
-
getAll(): ToolDefinition[];
|
|
462
|
-
getTools(): Omit<ToolDefinition, 'handler'>[];
|
|
463
|
-
has(name: string): boolean;
|
|
464
|
-
getSize(): number;
|
|
465
|
-
clear(): void;
|
|
466
|
-
}
|
|
467
|
-
declare const tools: ToolRegistry;
|
|
468
|
-
|
|
469
470
|
declare function getVersion(): string;
|
|
470
471
|
|
|
471
472
|
declare const NAME = "@easbot/mcp";
|
|
472
473
|
|
|
473
|
-
export { type AuthResult, type ClientInfo, HttpServerAdapter, type HttpServerConfig, type LocalMCPConfig, MCPClient, type MCPConfig, type McpConfig, type McpLocalConfig, type McpOAuthConfig, type McpRemoteConfig, NAME, type OAuthClientProviderAdapter, type OAuthConfig, type PromptMetadata, type RemoteMCPConfig, type RequestId, type ResourceMetadata, type ServerInfo, type ServerInstance$2 as ServerInstance, ServerStatus, ServerTransportType, Status, StdioServerAdapter, type StdioServerConfig, type ToolCallResult, type ToolContentItem, type ToolContext, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolListChangedHandler, type ToolMetadata, ToolRegistry, ToolRegistryEvent, createAndStartHttpServer, createAndStartStdioServer, createHttpServer, createMCPClient, createServerFromConfig, createServersFromConfigMap, createStdioServer, getVersion
|
|
474
|
+
export { type AuthResult, type ClientInfo, HttpServerAdapter, type HttpServerConfig, type LocalMCPConfig, MCPClient, type MCPConfig, type McpConfig, type McpLocalConfig, type McpOAuthConfig, type McpRemoteConfig, NAME, type OAuthClientProviderAdapter, type OAuthConfig, type PromptMetadata, type RemoteMCPConfig, type RequestId, type ResourceMetadata, type ServerInfo, type ServerInstance$2 as ServerInstance, ServerStatus, ServerTransportType, Status, StdioServerAdapter, type StdioServerConfig, type ToolCallResult, type ToolContentItem, type ToolContext, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolListChangedHandler, type ToolMetadata, ToolRegistry, ToolRegistryEvent, createAndStartHttpServer, createAndStartStdioServer, createHttpServer, createMCPClient, createServerFromConfig, createServersFromConfigMap, createStdioServer, getVersion };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
4
|
-
import z, { ZodType } from 'zod';
|
|
5
2
|
import { EventEmitter } from 'node:events';
|
|
6
3
|
import { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import z, { ZodType } from 'zod';
|
|
5
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
6
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
7
7
|
|
|
8
8
|
interface McpLocalConfig {
|
|
9
9
|
type: 'local';
|
|
@@ -82,6 +82,43 @@ interface ServerInstance$2 {
|
|
|
82
82
|
restart(): Promise<void>;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
type ToolInputSchema = ZodType | Record<string, unknown>;
|
|
86
|
+
type RequestId = string | number;
|
|
87
|
+
interface ToolContext {
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
sessionId?: string;
|
|
90
|
+
requestId: RequestId;
|
|
91
|
+
taskId?: string;
|
|
92
|
+
_meta?: Record<string, unknown>;
|
|
93
|
+
authInfo?: {
|
|
94
|
+
type: string;
|
|
95
|
+
claims?: Record<string, unknown>;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
type ToolHandler = (args: unknown, ctx: ToolContext) => Promise<CallToolResult>;
|
|
99
|
+
interface ToolDefinition extends Omit<Tool, 'inputSchema'> {
|
|
100
|
+
inputSchema?: ToolInputSchema;
|
|
101
|
+
handler: ToolHandler;
|
|
102
|
+
}
|
|
103
|
+
declare const ToolRegistryEvent: {
|
|
104
|
+
readonly REGISTERED: "mcp.tool.registered";
|
|
105
|
+
readonly UNREGISTERED: "mcp.tool.unregistered";
|
|
106
|
+
readonly CLEARED: "mcp.tool.cleared";
|
|
107
|
+
};
|
|
108
|
+
type ToolRegistryEvent = (typeof ToolRegistryEvent)[keyof typeof ToolRegistryEvent];
|
|
109
|
+
declare class ToolRegistry extends EventEmitter {
|
|
110
|
+
private tools;
|
|
111
|
+
constructor();
|
|
112
|
+
register(tool: ToolDefinition): void;
|
|
113
|
+
unregister(name: string): boolean;
|
|
114
|
+
get(name: string): ToolDefinition | undefined;
|
|
115
|
+
getAll(): ToolDefinition[];
|
|
116
|
+
getTools(): Omit<ToolDefinition, 'handler'>[];
|
|
117
|
+
has(name: string): boolean;
|
|
118
|
+
getSize(): number;
|
|
119
|
+
clear(): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
85
122
|
interface ServerInstance$1 {
|
|
86
123
|
info: ServerInfo;
|
|
87
124
|
get server(): McpServer | null;
|
|
@@ -96,7 +133,8 @@ declare class StdioServerAdapter {
|
|
|
96
133
|
private isStarting;
|
|
97
134
|
private isStopping;
|
|
98
135
|
private toolEventCleanup;
|
|
99
|
-
|
|
136
|
+
private readonly tools;
|
|
137
|
+
constructor(config: StdioServerConfig, tools: ToolRegistry);
|
|
100
138
|
getInfo(): ServerInfo;
|
|
101
139
|
getConfig(): StdioServerConfig;
|
|
102
140
|
createInstance(): ServerInstance$1;
|
|
@@ -111,8 +149,8 @@ declare class StdioServerAdapter {
|
|
|
111
149
|
stop(): Promise<void>;
|
|
112
150
|
restart(): Promise<void>;
|
|
113
151
|
}
|
|
114
|
-
declare function createStdioServer(config: StdioServerConfig): StdioServerAdapter;
|
|
115
|
-
declare function createAndStartStdioServer(config: StdioServerConfig): Promise<ServerInstance$1>;
|
|
152
|
+
declare function createStdioServer(config: StdioServerConfig, tools: ToolRegistry): StdioServerAdapter;
|
|
153
|
+
declare function createAndStartStdioServer(config: StdioServerConfig, tools: ToolRegistry): Promise<ServerInstance$1>;
|
|
116
154
|
|
|
117
155
|
interface ServerInstance {
|
|
118
156
|
info: ServerInfo;
|
|
@@ -130,7 +168,8 @@ declare class HttpServerAdapter {
|
|
|
130
168
|
private isStarting;
|
|
131
169
|
private isStopping;
|
|
132
170
|
private toolCleanup;
|
|
133
|
-
|
|
171
|
+
private readonly tools;
|
|
172
|
+
constructor(config: HttpServerConfig, tools: ToolRegistry);
|
|
134
173
|
getInfo(): ServerInfo;
|
|
135
174
|
getConfig(): HttpServerConfig;
|
|
136
175
|
createInstance(): ServerInstance;
|
|
@@ -145,11 +184,11 @@ declare class HttpServerAdapter {
|
|
|
145
184
|
stop(): Promise<void>;
|
|
146
185
|
restart(): Promise<void>;
|
|
147
186
|
}
|
|
148
|
-
declare function createHttpServer(config: HttpServerConfig): HttpServerAdapter;
|
|
149
|
-
declare function createAndStartHttpServer(config: HttpServerConfig): Promise<ServerInstance>;
|
|
187
|
+
declare function createHttpServer(config: HttpServerConfig, tools: ToolRegistry): HttpServerAdapter;
|
|
188
|
+
declare function createAndStartHttpServer(config: HttpServerConfig, tools: ToolRegistry): Promise<ServerInstance>;
|
|
150
189
|
|
|
151
|
-
declare function createServerFromConfig(name: string, config: McpConfig): Promise<ServerInstance$2>;
|
|
152
|
-
declare function createServersFromConfigMap(configs: Record<string, McpConfig
|
|
190
|
+
declare function createServerFromConfig(name: string, config: McpConfig, tools: ToolRegistry): Promise<ServerInstance$2>;
|
|
191
|
+
declare function createServersFromConfigMap(configs: Record<string, McpConfig>, tools: ToolRegistry): Promise<Record<string, ServerInstance$2>>;
|
|
153
192
|
|
|
154
193
|
interface OAuthConfig {
|
|
155
194
|
clientId?: string;
|
|
@@ -428,46 +467,8 @@ declare class MCPClient {
|
|
|
428
467
|
}
|
|
429
468
|
declare function createMCPClient(): MCPClient;
|
|
430
469
|
|
|
431
|
-
type ToolInputSchema = ZodType | Record<string, unknown>;
|
|
432
|
-
type RequestId = string | number;
|
|
433
|
-
interface ToolContext {
|
|
434
|
-
signal?: AbortSignal;
|
|
435
|
-
sessionId?: string;
|
|
436
|
-
requestId: RequestId;
|
|
437
|
-
taskId?: string;
|
|
438
|
-
_meta?: Record<string, unknown>;
|
|
439
|
-
authInfo?: {
|
|
440
|
-
type: string;
|
|
441
|
-
claims?: Record<string, unknown>;
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
type ToolHandler = (args: unknown, ctx: ToolContext) => Promise<CallToolResult>;
|
|
445
|
-
interface ToolDefinition extends Omit<Tool, 'inputSchema'> {
|
|
446
|
-
inputSchema?: ToolInputSchema;
|
|
447
|
-
handler: ToolHandler;
|
|
448
|
-
}
|
|
449
|
-
declare const ToolRegistryEvent: {
|
|
450
|
-
readonly REGISTERED: "mcp.tool.registered";
|
|
451
|
-
readonly UNREGISTERED: "mcp.tool.unregistered";
|
|
452
|
-
readonly CLEARED: "mcp.tool.cleared";
|
|
453
|
-
};
|
|
454
|
-
type ToolRegistryEvent = (typeof ToolRegistryEvent)[keyof typeof ToolRegistryEvent];
|
|
455
|
-
declare class ToolRegistry extends EventEmitter {
|
|
456
|
-
private tools;
|
|
457
|
-
constructor();
|
|
458
|
-
register(tool: ToolDefinition): void;
|
|
459
|
-
unregister(name: string): boolean;
|
|
460
|
-
get(name: string): ToolDefinition | undefined;
|
|
461
|
-
getAll(): ToolDefinition[];
|
|
462
|
-
getTools(): Omit<ToolDefinition, 'handler'>[];
|
|
463
|
-
has(name: string): boolean;
|
|
464
|
-
getSize(): number;
|
|
465
|
-
clear(): void;
|
|
466
|
-
}
|
|
467
|
-
declare const tools: ToolRegistry;
|
|
468
|
-
|
|
469
470
|
declare function getVersion(): string;
|
|
470
471
|
|
|
471
472
|
declare const NAME = "@easbot/mcp";
|
|
472
473
|
|
|
473
|
-
export { type AuthResult, type ClientInfo, HttpServerAdapter, type HttpServerConfig, type LocalMCPConfig, MCPClient, type MCPConfig, type McpConfig, type McpLocalConfig, type McpOAuthConfig, type McpRemoteConfig, NAME, type OAuthClientProviderAdapter, type OAuthConfig, type PromptMetadata, type RemoteMCPConfig, type RequestId, type ResourceMetadata, type ServerInfo, type ServerInstance$2 as ServerInstance, ServerStatus, ServerTransportType, Status, StdioServerAdapter, type StdioServerConfig, type ToolCallResult, type ToolContentItem, type ToolContext, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolListChangedHandler, type ToolMetadata, ToolRegistry, ToolRegistryEvent, createAndStartHttpServer, createAndStartStdioServer, createHttpServer, createMCPClient, createServerFromConfig, createServersFromConfigMap, createStdioServer, getVersion
|
|
474
|
+
export { type AuthResult, type ClientInfo, HttpServerAdapter, type HttpServerConfig, type LocalMCPConfig, MCPClient, type MCPConfig, type McpConfig, type McpLocalConfig, type McpOAuthConfig, type McpRemoteConfig, NAME, type OAuthClientProviderAdapter, type OAuthConfig, type PromptMetadata, type RemoteMCPConfig, type RequestId, type ResourceMetadata, type ServerInfo, type ServerInstance$2 as ServerInstance, ServerStatus, ServerTransportType, Status, StdioServerAdapter, type StdioServerConfig, type ToolCallResult, type ToolContentItem, type ToolContext, type ToolDefinition, type ToolHandler, type ToolInputSchema, type ToolListChangedHandler, type ToolMetadata, ToolRegistry, ToolRegistryEvent, createAndStartHttpServer, createAndStartStdioServer, createHttpServer, createMCPClient, createServerFromConfig, createServersFromConfigMap, createStdioServer, getVersion };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {McpServer}from'@modelcontextprotocol/sdk/server/mcp.js';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {Log,withTimeout}from'@easbot/utils';import {EventEmitter}from'events';import d from'zod/v4';import K from'http';import {StreamableHTTPServerTransport}from'@modelcontextprotocol/sdk/server/streamableHttp.js';import {Client}from'@modelcontextprotocol/sdk/client/index.js';import {StdioClientTransport}from'@modelcontextprotocol/sdk/client/stdio.js';import {StreamableHTTPClientTransport}from'@modelcontextprotocol/sdk/client/streamableHttp.js';import {SSEClientTransport}from'@modelcontextprotocol/sdk/client/sse.js';import {CallToolResultSchema,ToolListChangedNotificationSchema}from'@modelcontextprotocol/sdk/types.js';import {UnauthorizedError}from'@modelcontextprotocol/sdk/client/auth.js';import v from'zod';var _=Object.defineProperty;var N=(s,t,e)=>t in s?_(s,t,{enumerable:true,configurable:true,writable:true,value:e}):s[t]=e;var a=(s,t,e)=>N(s,typeof t!="symbol"?t+"":t,e);var I={STDIO:"stdio",HTTP:"http"},l={STOPPED:"stopped",STARTING:"starting",RUNNING:"running",STOPPING:"stopping",ERROR:"error"};var U=Log.create({service:"tool-registry"}),b={REGISTERED:"mcp.tool.registered",UNREGISTERED:"mcp.tool.unregistered",CLEARED:"mcp.tool.cleared"},k=class extends EventEmitter{constructor(){super();a(this,"tools",new Map);}register(e){if(!e.name||typeof e.name!="string")throw new Error("\u5DE5\u5177\u540D\u79F0\u65E0\u6548\uFF1A\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(this.tools.has(e.name)){U?.debug("tool already registered, skipping",{tool:e.name});return}this.tools.set(e.name,e),this.emit(b.REGISTERED,e);}unregister(e){return this.tools.has(e)?(this.tools.delete(e),this.emit(b.UNREGISTERED,e),true):false}get(e){return this.tools.get(e)}getAll(){return Array.from(this.tools.values())}getTools(){return Array.from(this.tools.values()).map(({handler:e,...r})=>r)}has(e){return this.tools.has(e)}getSize(){return this.tools.size}clear(){this.tools.clear(),this.emit(b.CLEARED);}},C=new k;var y=Log.create({service:"mcp.server.stdio"}),R=class{constructor(t){a(this,"config");a(this,"server",null);a(this,"info");a(this,"isStarting",false);a(this,"isStopping",false);a(this,"toolEventCleanup",null);a(this,"handleToolRegistered",t=>{if(this.server){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),y.info("tool registered",{tool:t.name});}});this.config=t,this.info={id:t.id,name:t.name,version:t.version,transportType:t.transportType,status:l.STOPPED,createdAt:Date.now(),lastActivityAt:Date.now(),enabled:t.enabled};}getInfo(){return this.info}getConfig(){return this.config}createInstance(){let t=this;return {info:this.info,get server(){return t.server},start:()=>this.start(),stop:()=>this.stop(),restart:()=>this.restart()}}loadTools(){if(this.server)for(let t of C.getAll()){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),y.info("tool loaded",{tool:t.name});}}subscribeToolEvents(){this.toolEventCleanup=()=>{C.off(b.REGISTERED,this.handleToolRegistered);},C.on(b.REGISTERED,this.handleToolRegistered);}adaptToolHandler(t){return async(e,r)=>{let n={signal:r.signal,sessionId:r.sessionId,requestId:r.requestId,taskId:r.taskId,_meta:r._meta,authInfo:r.authInfo?{type:"bearer",claims:{clientId:r.authInfo.clientId,scopes:r.authInfo.scopes}}:void 0};return t(e,n)}}unsubscribeToolEvents(){this.toolEventCleanup&&(this.toolEventCleanup(),this.toolEventCleanup=null);}convertToZodSchema(t){if(t instanceof d.ZodType)return t;if(typeof t=="object"&&t!==null){let e=t;if(e.type==="object"){let r=e.properties||{},n=e.required||[],o={};for(let[i,m]of Object.entries(r))o[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!n.includes(i)).length===0)return d.object(o);let f={};for(let[i,m]of Object.entries(r)){let c=o[i];c&&(n.includes(i)?f[i]=c:f[i]=c.optional());}return d.object(f)}if(e.type==="string")return d.string();if(e.type==="number")return d.number();if(e.type==="boolean")return d.boolean();if(e.type==="array"){let r=e.items;return r?d.array(this.jsonSchemaToZod(r)):d.array(d.unknown())}}return d.unknown()}jsonSchemaToZod(t){switch(t.type){case "string":return d.string();case "number":return d.number();case "integer":return d.number().int();case "boolean":return d.boolean();case "null":return d.null();case "array":{let r=t.items;return r?d.array(this.jsonSchemaToZod(r)):d.array(d.unknown())}case "object":{let r=t.properties||{},n=t.required||[],o={};for(let[i,m]of Object.entries(r))o[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!n.includes(i)).length===0)return d.object(o);let f={};for(let[i,m]of Object.entries(r)){let c=o[i];c&&(n.includes(i)?f[i]=c:f[i]=c.optional());}return d.object(f)}default:return d.unknown()}}async start(){if(this.isStarting){y.warn("server is already starting",{id:this.info.id});return}if(this.info.status===l.RUNNING){y.debug("server is already running",{id:this.info.id});return}this.isStarting=true,this.info.status=l.STARTING;try{y.info("starting stdio mcp server",{id:this.info.id,name:this.info.name,command:this.config.command,args:this.config.args});let t=new StdioServerTransport;this.server=new McpServer({name:this.config.name,version:this.config.version}),this.loadTools(),this.subscribeToolEvents(),await this.server.connect(t),this.info.status=l.RUNNING,this.info.lastActivityAt=Date.now(),y.info("stdio mcp server started successfully",{id:this.info.id,name:this.info.name,toolCount:C.getSize()});}catch(t){throw this.info.status=l.ERROR,y.error("failed to start stdio mcp server",{id:this.info.id,error:t instanceof Error?t.message:String(t)}),t}finally{this.isStarting=false;}}async stop(){if(this.isStopping){y.warn("server is already stopping",{id:this.info.id});return}if(this.info.status!==l.RUNNING&&this.info.status!==l.ERROR){y.debug("server is not running",{id:this.info.id,status:this.info.status});return}this.isStopping=true,this.info.status=l.STOPPING;try{y.info("stopping stdio mcp server",{id:this.info.id,name:this.info.name}),this.unsubscribeToolEvents(),this.server&&(await this.server.close(),this.server=null),this.info.status=l.STOPPED,y.info("stdio mcp server stopped",{id:this.info.id,name:this.info.name});}catch(t){this.info.status=l.ERROR,y.error("error stopping stdio mcp server",{id:this.info.id,error:t instanceof Error?t.message:String(t)});}finally{this.isStopping=false;}}async restart(){y.info("restarting stdio mcp server",{id:this.info.id}),await this.stop(),await this.start();}};function G(s){return new R(s)}async function V(s){let t=new R(s);return await t.start(),t.createInstance()}var g=Log.create({service:"mcp.server.http"}),w=class{constructor(t){a(this,"config");a(this,"server",null);a(this,"transport",null);a(this,"httpServer",null);a(this,"info");a(this,"isStarting",false);a(this,"isStopping",false);a(this,"toolCleanup",null);a(this,"handleTool",t=>{if(this.server){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),g.info("tool registered",{tool:t.name});}});this.config=t,this.info={id:t.id,name:t.name,version:t.version,transportType:t.transportType,status:l.STOPPED,createdAt:Date.now(),lastActivityAt:Date.now()};}getInfo(){return this.info}getConfig(){return this.config}createInstance(){let t=this;return {info:this.info,get server(){return t.server},start:()=>this.start(),stop:()=>this.stop(),restart:()=>this.restart()}}loadTools(){if(this.server)for(let t of C.getAll()){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),g.info("tool loaded",{tool:t.name});}}subscribeTools(){this.toolCleanup=()=>{C.off(b.REGISTERED,this.handleTool);},C.on(b.REGISTERED,this.handleTool);}convertToZodSchema(t){if(t instanceof d.ZodType)return t;if(typeof t=="object"&&t!==null){let e=t;if(e.type==="object"){let r=e.properties||{},n=e.required||[],o={};for(let[i,m]of Object.entries(r))o[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!n.includes(i)).length===0)return d.object(o);let f={};for(let[i,m]of Object.entries(r)){let c=o[i];c&&(n.includes(i)?f[i]=c:f[i]=c.optional());}return d.object(f)}if(e.type==="string")return d.string();if(e.type==="number")return d.number();if(e.type==="boolean")return d.boolean();if(e.type==="array"){let r=e.items;return r?d.array(this.jsonSchemaToZod(r)):d.array(d.unknown())}}return d.unknown()}jsonSchemaToZod(t){switch(t.type){case "string":return d.string();case "number":return d.number();case "integer":return d.number().int();case "boolean":return d.boolean();case "null":return d.null();case "array":{let r=t.items;return r?d.array(this.jsonSchemaToZod(r)):d.array(d.unknown())}case "object":{let r=t.properties||{},n=t.required||[],o={};for(let[i,m]of Object.entries(r))o[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!n.includes(i)).length===0)return d.object(o);let f={};for(let[i,m]of Object.entries(r)){let c=o[i];c&&(n.includes(i)?f[i]=c:f[i]=c.optional());}return d.object(f)}default:return d.unknown()}}adaptToolHandler(t){return async(e,r)=>{let n={signal:r.signal,sessionId:r.sessionId,requestId:r.requestId,taskId:r.taskId,_meta:r._meta,authInfo:r.authInfo?{type:"bearer",claims:{clientId:r.authInfo.clientId,scopes:r.authInfo.scopes}}:void 0};return t(e,n)}}unsubscribeTools(){this.toolCleanup&&(this.toolCleanup(),this.toolCleanup=null);}async start(){if(this.isStarting){g.warn("already starting",{id:this.info.id});return}if(this.info.status===l.RUNNING){g.debug("already running",{id:this.info.id});return}this.isStarting=true,this.info.status=l.STARTING;try{g.info("starting",{id:this.info.id,name:this.info.name,url:this.config.url});let t;try{t=new URL(this.config.url);}catch{throw new Error(`\u65E0\u6548\u7684 URL: ${this.config.url}`)}let e=parseInt(t.port||"3000",10),r=t.hostname||"0.0.0.0";if(Number.isNaN(e)||e<1||e>65535)throw new Error(`\u65E0\u6548\u7684\u7AEF\u53E3\u53F7: ${t.port}`);this.transport=new StreamableHTTPServerTransport({sessionIdGenerator:()=>crypto.randomUUID()}),this.server=new McpServer({name:this.config.name,version:this.config.version}),this.loadTools(),this.subscribeTools(),await this.server.connect(this.transport),this.httpServer=K.createServer((n,o)=>{let h=n.headers.origin,f=(m,c,A)=>{o.writeHead(m,c),A?o.end(A):o.end();},i={"Access-Control-Allow-Origin":h||"*","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, mcpsessionid, mcp-protocol-version","Access-Control-Max-Age":"86400"};if(n.method==="OPTIONS"){f(204,i);return}if(n.method==="POST"){let m="";n.setEncoding("utf8"),n.on("data",c=>{m+=c;}),n.on("end",()=>{let c;if(m)try{c=JSON.parse(m);}catch{c=void 0;}this.transport&&this.transport.handleRequest(n,o,c);}),n.on("error",c=>{g.error("HTTP request error",{error:c.message}),f(500,i,JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal error"}}));});}else n.method==="GET"?this.transport&&this.transport.handleRequest(n,o,void 0):f(405,i,JSON.stringify({jsonrpc:"2.0",error:{code:-32601,message:"Method not found"}}));}),this.httpServer.on("error",n=>{g.error("HTTP server error",{error:n.message}),this.info.status=l.ERROR;}),this.httpServer.on("clientError",(n,o)=>{g.warn("HTTP client error",{error:n.message}),o.writable&&o.end(`HTTP/1.1 400 Bad Request\r
|
|
1
|
+
import {McpServer}from'@modelcontextprotocol/sdk/server/mcp.js';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {Log,withTimeout}from'@easbot/utils';import {EventEmitter}from'events';import d from'zod';import V from'http';import {StreamableHTTPServerTransport}from'@modelcontextprotocol/sdk/server/streamableHttp.js';import u from'zod/v4';import {Client}from'@modelcontextprotocol/sdk/client/index.js';import {StdioClientTransport}from'@modelcontextprotocol/sdk/client/stdio.js';import {StreamableHTTPClientTransport}from'@modelcontextprotocol/sdk/client/streamableHttp.js';import {SSEClientTransport}from'@modelcontextprotocol/sdk/client/sse.js';import {CallToolResultSchema,ToolListChangedNotificationSchema}from'@modelcontextprotocol/sdk/types.js';import {UnauthorizedError}from'@modelcontextprotocol/sdk/client/auth.js';var j=Object.defineProperty;var _=(s,t,e)=>t in s?j(s,t,{enumerable:true,configurable:true,writable:true,value:e}):s[t]=e;var a=(s,t,e)=>_(s,typeof t!="symbol"?t+"":t,e);var P={STDIO:"stdio",HTTP:"http"},l={STOPPED:"stopped",STARTING:"starting",RUNNING:"running",STOPPING:"stopping",ERROR:"error"};var L=Log.create({service:"tool-registry"}),C={REGISTERED:"mcp.tool.registered",UNREGISTERED:"mcp.tool.unregistered",CLEARED:"mcp.tool.cleared"},k=class extends EventEmitter{constructor(){super();a(this,"tools",new Map);}register(e){if(!e.name||typeof e.name!="string")throw new Error("\u5DE5\u5177\u540D\u79F0\u65E0\u6548\uFF1A\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");if(this.tools.has(e.name)){L?.debug("tool already registered, skipping",{tool:e.name});return}this.tools.set(e.name,e),this.emit(C.REGISTERED,e);}unregister(e){return this.tools.has(e)?(this.tools.delete(e),this.emit(C.UNREGISTERED,e),true):false}get(e){return this.tools.get(e)}getAll(){return Array.from(this.tools.values())}getTools(){return Array.from(this.tools.values()).map(({handler:e,...r})=>r)}has(e){return this.tools.has(e)}getSize(){return this.tools.size}clear(){this.tools.clear(),this.emit(C.CLEARED);}};var y=Log.create({service:"mcp.server.stdio"}),R=class{constructor(t,e){a(this,"config");a(this,"server",null);a(this,"info");a(this,"isStarting",false);a(this,"isStopping",false);a(this,"toolEventCleanup",null);a(this,"tools");a(this,"handleToolRegistered",t=>{if(this.server){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),y.info("tool registered",{tool:t.name});}});this.config=t,this.tools=e,this.info={id:t.id,name:t.name,version:t.version,transportType:t.transportType,status:l.STOPPED,createdAt:Date.now(),lastActivityAt:Date.now(),enabled:t.enabled};}getInfo(){return this.info}getConfig(){return this.config}createInstance(){let t=this;return {info:this.info,get server(){return t.server},start:()=>this.start(),stop:()=>this.stop(),restart:()=>this.restart()}}loadTools(){if(this.server)for(let t of this.tools.getAll()){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),y.info("tool loaded",{tool:t.name});}}subscribeToolEvents(){this.toolEventCleanup=()=>{this.tools.off(C.REGISTERED,this.handleToolRegistered);},this.tools.on(C.REGISTERED,this.handleToolRegistered);}adaptToolHandler(t){return async(e,r)=>{let o={signal:r.signal,sessionId:r.sessionId,requestId:r.requestId,taskId:r.taskId,_meta:r._meta,authInfo:r.authInfo?{type:"bearer",claims:{clientId:r.authInfo.clientId,scopes:r.authInfo.scopes}}:void 0};return t(e,o)}}unsubscribeToolEvents(){this.toolEventCleanup&&(this.toolEventCleanup(),this.toolEventCleanup=null);}convertToZodSchema(t){if(t instanceof d.ZodType)return t;if(typeof t=="object"&&t!==null){let e=t;if(e.type==="object"){let r=e.properties||{},o=e.required||[],n={};for(let[i,m]of Object.entries(r))n[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!o.includes(i)).length===0)return d.object(n);let f={};for(let[i,m]of Object.entries(r)){let c=n[i];c&&(o.includes(i)?f[i]=c:f[i]=c.optional());}return d.object(f)}if(e.type==="string")return d.string();if(e.type==="number")return d.number();if(e.type==="boolean")return d.boolean();if(e.type==="array"){let r=e.items;return r?d.array(this.jsonSchemaToZod(r)):d.array(d.unknown())}}return d.unknown()}jsonSchemaToZod(t){switch(t.type){case "string":return d.string();case "number":return d.number();case "integer":return d.number().int();case "boolean":return d.boolean();case "null":return d.null();case "array":{let r=t.items;return r?d.array(this.jsonSchemaToZod(r)):d.array(d.unknown())}case "object":{let r=t.properties||{},o=t.required||[],n={};for(let[i,m]of Object.entries(r))n[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!o.includes(i)).length===0)return d.object(n);let f={};for(let[i,m]of Object.entries(r)){let c=n[i];c&&(o.includes(i)?f[i]=c:f[i]=c.optional());}return d.object(f)}default:return d.unknown()}}async start(){if(this.isStarting){y.warn("server is already starting",{id:this.info.id});return}if(this.info.status===l.RUNNING){y.debug("server is already running",{id:this.info.id});return}this.isStarting=true,this.info.status=l.STARTING;try{y.info("starting stdio mcp server",{id:this.info.id,name:this.info.name,command:this.config.command,args:this.config.args});let t=new StdioServerTransport;this.server=new McpServer({name:this.config.name,version:this.config.version}),this.loadTools(),this.subscribeToolEvents(),await this.server.connect(t),this.info.status=l.RUNNING,this.info.lastActivityAt=Date.now(),y.info("stdio mcp server started successfully",{id:this.info.id,name:this.info.name,toolCount:this.tools.getSize()});}catch(t){throw this.info.status=l.ERROR,y.error("failed to start stdio mcp server",{id:this.info.id,error:t instanceof Error?t.message:String(t)}),t}finally{this.isStarting=false;}}async stop(){if(this.isStopping){y.warn("server is already stopping",{id:this.info.id});return}if(this.info.status!==l.RUNNING&&this.info.status!==l.ERROR){y.debug("server is not running",{id:this.info.id,status:this.info.status});return}this.isStopping=true,this.info.status=l.STOPPING;try{y.info("stopping stdio mcp server",{id:this.info.id,name:this.info.name}),this.unsubscribeToolEvents(),this.server&&(await this.server.close(),this.server=null),this.info.status=l.STOPPED,y.info("stdio mcp server stopped",{id:this.info.id,name:this.info.name});}catch(t){this.info.status=l.ERROR,y.error("error stopping stdio mcp server",{id:this.info.id,error:t instanceof Error?t.message:String(t)});}finally{this.isStopping=false;}}async restart(){y.info("restarting stdio mcp server",{id:this.info.id}),await this.stop(),await this.start();}};function q(s,t){return new R(s,t)}async function G(s,t){let e=new R(s,t);return await e.start(),e.createInstance()}var g=Log.create({service:"mcp.server.http"}),b=class{constructor(t,e){a(this,"config");a(this,"server",null);a(this,"transport",null);a(this,"httpServer",null);a(this,"info");a(this,"isStarting",false);a(this,"isStopping",false);a(this,"toolCleanup",null);a(this,"tools");a(this,"handleTool",t=>{if(this.server){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),g.info("tool registered",{tool:t.name});}});this.config=t,this.tools=e,this.info={id:t.id,name:t.name,version:t.version,transportType:t.transportType,status:l.STOPPED,createdAt:Date.now(),lastActivityAt:Date.now()};}getInfo(){return this.info}getConfig(){return this.config}createInstance(){let t=this;return {info:this.info,get server(){return t.server},start:()=>this.start(),stop:()=>this.stop(),restart:()=>this.restart()}}loadTools(){if(this.server)for(let t of this.tools.getAll()){let e=this.convertToZodSchema(t.inputSchema),r=this.adaptToolHandler(t.handler);this.server.registerTool(t.name,{description:t.description,inputSchema:e},r),g.info("tool loaded",{tool:t.name});}}subscribeTools(){this.toolCleanup=()=>{this.tools.off(C.REGISTERED,this.handleTool);},this.tools.on(C.REGISTERED,this.handleTool);}convertToZodSchema(t){if(t instanceof u.ZodType)return t;if(typeof t=="object"&&t!==null){let e=t;if(e.type==="object"){let r=e.properties||{},o=e.required||[],n={};for(let[i,m]of Object.entries(r))n[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!o.includes(i)).length===0)return u.object(n);let f={};for(let[i,m]of Object.entries(r)){let c=n[i];c&&(o.includes(i)?f[i]=c:f[i]=c.optional());}return u.object(f)}if(e.type==="string")return u.string();if(e.type==="number")return u.number();if(e.type==="boolean")return u.boolean();if(e.type==="array"){let r=e.items;return r?u.array(this.jsonSchemaToZod(r)):u.array(u.unknown())}}return u.unknown()}jsonSchemaToZod(t){switch(t.type){case "string":return u.string();case "number":return u.number();case "integer":return u.number().int();case "boolean":return u.boolean();case "null":return u.null();case "array":{let r=t.items;return r?u.array(this.jsonSchemaToZod(r)):u.array(u.unknown())}case "object":{let r=t.properties||{},o=t.required||[],n={};for(let[i,m]of Object.entries(r))n[i]=this.jsonSchemaToZod(m);if(Object.keys(r).filter(i=>!o.includes(i)).length===0)return u.object(n);let f={};for(let[i,m]of Object.entries(r)){let c=n[i];c&&(o.includes(i)?f[i]=c:f[i]=c.optional());}return u.object(f)}default:return u.unknown()}}adaptToolHandler(t){return async(e,r)=>{let o={signal:r.signal,sessionId:r.sessionId,requestId:r.requestId,taskId:r.taskId,_meta:r._meta,authInfo:r.authInfo?{type:"bearer",claims:{clientId:r.authInfo.clientId,scopes:r.authInfo.scopes}}:void 0};return t(e,o)}}unsubscribeTools(){this.toolCleanup&&(this.toolCleanup(),this.toolCleanup=null);}async start(){if(this.isStarting){g.warn("already starting",{id:this.info.id});return}if(this.info.status===l.RUNNING){g.debug("already running",{id:this.info.id});return}this.isStarting=true,this.info.status=l.STARTING;try{g.info("starting",{id:this.info.id,name:this.info.name,url:this.config.url});let t;try{t=new URL(this.config.url);}catch{throw new Error(`\u65E0\u6548\u7684 URL: ${this.config.url}`)}let e=parseInt(t.port||"3000",10),r=t.hostname||"0.0.0.0";if(Number.isNaN(e)||e<1||e>65535)throw new Error(`\u65E0\u6548\u7684\u7AEF\u53E3\u53F7: ${t.port}`);this.transport=new StreamableHTTPServerTransport({sessionIdGenerator:()=>crypto.randomUUID()}),this.server=new McpServer({name:this.config.name,version:this.config.version}),this.loadTools(),this.subscribeTools(),await this.server.connect(this.transport),this.httpServer=V.createServer((o,n)=>{let h=o.headers.origin,f=(m,c,M)=>{n.writeHead(m,c),M?n.end(M):n.end();},i={"Access-Control-Allow-Origin":h||"*","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, mcpsessionid, mcp-protocol-version","Access-Control-Max-Age":"86400"};if(o.method==="OPTIONS"){f(204,i);return}if(o.method==="POST"){let m="";o.setEncoding("utf8"),o.on("data",c=>{m+=c;}),o.on("end",()=>{let c;if(m)try{c=JSON.parse(m);}catch{c=void 0;}this.transport&&this.transport.handleRequest(o,n,c);}),o.on("error",c=>{g.error("HTTP request error",{error:c.message}),f(500,i,JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal error"}}));});}else o.method==="GET"?this.transport&&this.transport.handleRequest(o,n,void 0):f(405,i,JSON.stringify({jsonrpc:"2.0",error:{code:-32601,message:"Method not found"}}));}),this.httpServer.on("error",o=>{g.error("HTTP server error",{error:o.message}),this.info.status=l.ERROR;}),this.httpServer.on("clientError",(o,n)=>{g.warn("HTTP client error",{error:o.message}),n.writable&&n.end(`HTTP/1.1 400 Bad Request\r
|
|
2
2
|
\r
|
|
3
|
-
`);}),this.httpServer.listen(e,r),g.info("HTTP server listening",{hostname:r,port:e}),this.info.status=l.RUNNING,this.info.lastActivityAt=Date.now(),g.info("started",{id:this.info.id,name:this.info.name,toolCount:C.getSize()});}catch(t){throw this.info.status=l.ERROR,g.error("failed to start",{id:this.info.id,error:t instanceof Error?t.message:String(t)}),t}finally{this.isStarting=false;}}async stop(){if(this.isStopping){g.warn("already stopping",{id:this.info.id});return}if(this.info.status!==l.RUNNING&&this.info.status!==l.ERROR){g.debug("not running",{id:this.info.id,status:this.info.status});return}this.isStopping=true,this.info.status=l.STOPPING;try{g.info("stopping",{id:this.info.id,name:this.info.name}),this.unsubscribeTools(),this.httpServer&&(await new Promise(t=>{this.httpServer.close(()=>t());}),this.httpServer=null),this.transport&&(await this.transport.close(),this.transport=null),this.server&&(await this.server.close(),this.server=null),this.info.status=l.STOPPED,g.info("stopped",{id:this.info.id});}catch(t){this.info.status=l.ERROR,g.error("failed to stop",{id:this.info.id,error:t instanceof Error?t.message:String(t)});}finally{this.isStopping=false;}}async restart(){g.info("restarting",{id:this.info.id}),await this.stop(),await this.start();}};function J(s){return new w(s)}async function W(s){let t=new w(s);return await t.start(),t.createInstance()}var H=Log.create({service:"mcp.server.factory"});function X(s){return s.type==="local"}function Y(s){return s.type==="remote"}function tt(s,t){if(!t.command)throw new Error(`MCP server ${s} has no command specified`);return {id:s,name:s,version:"1.0.0",transportType:I.STDIO,enabled:t.enabled,timeout:t.timeout,command:t.command,args:(t?.args?.length??0)>0?t.args:void 0,env:t.env}}function et(s,t){return {id:s,name:s,version:"1.0.0",transportType:I.HTTP,enabled:t.enabled,timeout:t.timeout,url:t.url,headers:t.headers}}async function rt(s,t){if(H.debug("creating server from config",{name:s,type:t.type}),X(t)){let e=tt(s,t),r=new R(e);return await r.start(),r.createInstance()}if(Y(t)){let e=et(s,t),r=new w(e);return await r.start(),r.createInstance()}throw new Error(`Unsupported MCP config type: ${t.type}`)}async function Gt(s){let t={};for(let[e,r]of Object.entries(s))try{let n=await rt(e,r);t[e]=n;}catch(n){H.error("failed to create server from config",{name:e,error:n instanceof Error?n.message:String(n)});}return t}var O={version:"0.3.0"};function E(){return O.version}var p=Log.create({service:"mcp.client"}),x=6e4,M=class{constructor(){a(this,"id","");a(this,"client",null);a(this,"_info");a(this,"notificationCleanup",null);a(this,"pendingTransport",null);a(this,"oauthState","");a(this,"capturedRedirectUrl",null);a(this,"oauthConfig");a(this,"toolListChangedHandler",null);a(this,"oauthProvider",null);this._info=this.createInitialInfo();}get info(){return {...this._info}}get status(){return this._info.status}get tools(){return [...this._info.tools]}get resources(){return [...this._info.resources]}get prompts(){return [...this._info.prompts]}get clientId(){return this.id}setToolListChangedHandler(t){this.toolListChangedHandler=t;}setOAuthProvider(t){this.oauthProvider=t;}async connect(t,e){p.info("connecting to MCP server",{id:t,type:e.type}),await this.disconnect(),this.id=t,this._info=this.createInitialInfo(),this._info.id=t,this._info.name=t;try{if(e.type==="remote"&&(this.oauthConfig=e.oauth===!1?void 0:e.oauth??{}),this.client=await this.createClient(e),!this.client)return;this._info.status={status:"connected"},this._info.connectedAt=Date.now(),await this.fetchCapabilities(),p.info("connected to MCP server",{id:t,toolCount:this._info.tools.length});}catch(r){let n=r instanceof Error?r.message:String(r);this._info.status={status:"failed",error:n},this._info.error=n,p.error("failed to connect to MCP server",{id:t,error:n});}}async startAuth(){if(!this.pendingTransport)throw new Error("No pending OAuth flow. Please connect to a server that requires authentication first.");return this.oauthState=Array.from(crypto.getRandomValues(new Uint8Array(32))).map(t=>t.toString(16).padStart(2,"0")).join(""),this.oauthProvider&&await this.oauthProvider.saveState(this.oauthState),{authorizationUrl:this.capturedRedirectUrl?.toString()??"",state:this.oauthState}}async finishAuth(t){if(!this.pendingTransport)throw new Error("No pending OAuth flow.");try{await this.pendingTransport.finishAuth?.(t);}catch(e){throw p.error("failed to finish OAuth",{id:this.id,error:e}),e}this.pendingTransport=null,this.capturedRedirectUrl=null,p.info("OAuth completed, ready to reconnect",{id:this.id});}isAuthRequired(){return this._info.status.status==="needs_auth"}isClientRegistrationRequired(){return this._info.status.status==="needs_client_registration"}getPendingTransport(){return this.pendingTransport}getAuthorizationUrl(){return this.capturedRedirectUrl?.toString()??""}async disconnect(){this.notificationCleanup&&(this.notificationCleanup(),this.notificationCleanup=null),this.client&&(await this.client.close().catch(t=>{p.error("Failed to close MCP client",{id:this.id,error:t});}),this.client=null),this.pendingTransport=null,this.capturedRedirectUrl=null,this.toolListChangedHandler=null,this._info.status.status==="connected"&&(this._info.status={status:"disconnected"}),p.info("disconnected from MCP server",{id:this.id});}async listTools(){if(!this.client||this._info.status.status!=="connected")return {};let t={};for(let e of this._info.tools)t[e.name]={name:e.name,description:e.description,inputSchema:e.inputSchema};return t}async callTool(t,e,r){let n=Date.now();if(!this.client||this._info.status.status!=="connected")return {toolName:t,args:e,content:[],success:false,error:"Client not connected",duration:Date.now()-n};try{let o=await withTimeout(this.client.callTool({name:t,arguments:e},CallToolResultSchema,{resetTimeoutOnProgress:!0,timeout:r}),r??x);return {toolName:t,args:e,content:o.content.map(h=>h.type==="text"?{type:"text",text:h.text}:h.type==="image"?{type:"image",data:h.data,mimeType:h.mimeType}:h.type==="resource"?{type:"resource",resource:h.resource}:{type:"text",text:String(h)}),success:!0,duration:Date.now()-n,metadata:o.metadata}}catch(o){return {toolName:t,args:e,content:[],success:false,error:o instanceof Error?o.message:String(o),duration:Date.now()-n}}}async listResources(){return [...this._info.resources]}async readResource(t){if(!this.client||this._info.status.status!=="connected"){p.warn("client not connected for readResource",{id:this.id});return}return this.client.readResource({uri:t}).catch(e=>{p.error("failed to read resource",{id:this.id,uri:t,error:e.message});})}async listPrompts(){return [...this._info.prompts]}async getPrompt(t,e){if(!this.client||this._info.status.status!=="connected"){p.warn("client not connected for getPrompt",{id:this.id});return}return this.client.getPrompt({name:t,arguments:e}).catch(r=>{p.error("failed to get prompt",{id:this.id,name:t,error:r.message});})}async refreshTools(){if(!(!this.client||this._info.status.status!=="connected"))try{let t=await withTimeout(this.client.listTools(),x);this._info.tools=t.tools.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})),p.info("tools refreshed",{id:this.id,count:this._info.tools.length});}catch(t){p.error("failed to refresh tools",{id:this.id,error:t instanceof Error?t.message:String(t)});}}async createClient(t){return t.type==="local"?await this.createLocalClient(t):t.type==="remote"?await this.createRemoteClient(t):null}async fetchCapabilities(){if(this.client){try{let t=await withTimeout(this.client.listTools(),x);this._info.tools=t.tools.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}));}catch(t){p.debug("failed to get tools",{id:this.id,error:t instanceof Error?t.message:String(t)});}try{let t=await withTimeout(this.client.listResources(),5e3);this._info.resources=t.resources.map(e=>({name:e.name,uri:e.uri,description:e.description,mimeType:e.mimeType}));}catch{p.debug("failed to get resources",{id:this.id});}try{let t=await withTimeout(this.client.listPrompts(),5e3);this._info.prompts=t.prompts.map(e=>({name:e.name,description:e.description,arguments:e.arguments?.map(r=>({name:r.name,description:r.description,required:r.required}))}));}catch{p.debug("failed to get prompts",{id:this.id});}}}async createLocalClient(t){let[e,...r]=t.command;if(!e)return this._info.status={status:"failed",error:"Command is empty"},null;let n=new StdioClientTransport({stderr:"pipe",command:e,args:r.length>0?r:void 0,cwd:t.cwd??process.cwd(),env:Object.fromEntries(Object.entries({...process.env,...t.environment}).filter(([,o])=>o!==void 0))});n.stderr?.on("data",o=>{p.info(`mcp stderr: ${o.toString()}`,{id:this.id});});try{let o=new Client({name:"easbot-mcp",version:E()});return await withTimeout(o.connect(n),t.timeout??x),this.registerNotificationHandler(o),o}catch(o){let h=o instanceof Error?o.message:String(o);return this._info.status={status:"failed",error:h},p.error("local mcp startup failed",{id:this.id,command:[e,...r],error:h}),null}}async createRemoteClient(t){let e={headers:t.headers};if((t.transport||"streamable-http")==="sse"){p.info("using SSE transport",{id:this.id,url:t.url});let n=new SSEClientTransport(new URL(t.url),{requestInit:e});return await this.connectWithTransport(n,t)}else {p.info("using StreamableHTTP transport",{id:this.id,url:t.url});let n=new StreamableHTTPClientTransport(new URL(t.url),{requestInit:e});return await this.connectWithTransport(n,t)}}async connectWithTransport(t,e){try{let r=new Client({name:"easbot-mcp",version:E()});return await r.connect(t),this.registerNotificationHandler(r),r}catch(r){return this.handleConnectionError(r,t,e),null}}handleConnectionError(t,e,r){if(t instanceof UnauthorizedError){let n=t instanceof Error?t.message:String(t);n.includes("registration")||n.includes("client_id")?this._info.status={status:"needs_client_registration",error:"Server does not support dynamic client registration. Please provide clientId in config."}:(this._info.status={status:"needs_auth"},this.pendingTransport=e,this.capturedRedirectUrl=this.extractRedirectUrl(t));}else {let n=t instanceof Error?t.message:String(t);this._info.status={status:"failed",error:n};}p.error("remote mcp connection failed",{id:this.id,url:r.url,status:this._info.status});}extractRedirectUrl(t){let r=t.message.match(/https?:\/\/[^\s]+/);if(r)try{return new URL(r[0])}catch{return null}return null}registerNotificationHandler(t){let e=async()=>{p.info("tools list changed notification received",{id:this.id}),await this.refreshTools(),this.toolListChangedHandler&&await this.toolListChangedHandler();};t.setNotificationHandler(ToolListChangedNotificationSchema,e),this.notificationCleanup=()=>{t.removeNotificationHandler("notifications/tools/list_changed");};}createInitialInfo(){return {id:"",name:"",status:{status:"disconnected"},tools:[],resources:[],prompts:[]}}};function dt(){return new M}var me=v.discriminatedUnion("status",[v.object({status:v.literal("connected")}).meta({ref:"MCPStatusConnected"}),v.object({status:v.literal("disconnected")}).meta({ref:"MCPStatusDisconnected"}),v.object({status:v.literal("failed"),error:v.string()}).meta({ref:"MCPStatusFailed"}),v.object({status:v.literal("needs_auth")}).meta({ref:"MCPStatusNeedsAuth"}),v.object({status:v.literal("needs_client_registration"),error:v.string()}).meta({ref:"MCPStatusNeedsClientRegistration"}),v.object({status:v.literal("disabled")}).meta({ref:"MCPStatusDisabled"})]);var be="@easbot/mcp";export{w as HttpServerAdapter,M as MCPClient,be as NAME,l as ServerStatus,I as ServerTransportType,me as Status,R as StdioServerAdapter,k as ToolRegistry,b as ToolRegistryEvent,W as createAndStartHttpServer,V as createAndStartStdioServer,J as createHttpServer,dt as createMCPClient,rt as createServerFromConfig,Gt as createServersFromConfigMap,G as createStdioServer,E as getVersion,C as tools};
|
|
3
|
+
`);}),this.httpServer.listen(e,r),g.info("HTTP server listening",{hostname:r,port:e}),this.info.status=l.RUNNING,this.info.lastActivityAt=Date.now(),g.info("started",{id:this.info.id,name:this.info.name,toolCount:this.tools.getSize()});}catch(t){throw this.info.status=l.ERROR,g.error("failed to start",{id:this.info.id,error:t instanceof Error?t.message:String(t)}),t}finally{this.isStarting=false;}}async stop(){if(this.isStopping){g.warn("already stopping",{id:this.info.id});return}if(this.info.status!==l.RUNNING&&this.info.status!==l.ERROR){g.debug("not running",{id:this.info.id,status:this.info.status});return}this.isStopping=true,this.info.status=l.STOPPING;try{g.info("stopping",{id:this.info.id,name:this.info.name}),this.unsubscribeTools(),this.httpServer&&(await new Promise(t=>{this.httpServer.close(()=>t());}),this.httpServer=null),this.transport&&(await this.transport.close(),this.transport=null),this.server&&(await this.server.close(),this.server=null),this.info.status=l.STOPPED,g.info("stopped",{id:this.info.id});}catch(t){this.info.status=l.ERROR,g.error("failed to stop",{id:this.info.id,error:t instanceof Error?t.message:String(t)});}finally{this.isStopping=false;}}async restart(){g.info("restarting",{id:this.info.id}),await this.stop(),await this.start();}};function $(s,t){return new b(s,t)}async function J(s,t){let e=new b(s,t);return await e.start(),e.createInstance()}var A=Log.create({service:"mcp.server.factory"});function Q(s){return s.type==="local"}function X(s){return s.type==="remote"}function Y(s,t){if(!t.command)throw new Error(`MCP server ${s} has no command specified`);return {id:s,name:s,version:"1.0.0",transportType:P.STDIO,enabled:t.enabled,timeout:t.timeout,command:t.command,args:(t?.args?.length??0)>0?t.args:void 0,env:t.env}}function tt(s,t){return {id:s,name:s,version:"1.0.0",transportType:P.HTTP,enabled:t.enabled,timeout:t.timeout,url:t.url,headers:t.headers}}async function et(s,t,e){if(A.debug("creating server from config",{name:s,type:t.type}),Q(t)){let r=Y(s,t),o=new R(r,e);return await o.start(),o.createInstance()}if(X(t)){let r=tt(s,t),o=new b(r,e);return await o.start(),o.createInstance()}throw new Error(`Unsupported MCP config type: ${t.type}`)}async function Ft(s,t){let e={};for(let[r,o]of Object.entries(s))try{let n=await et(r,o,t);e[r]=n;}catch(n){A.error("failed to create server from config",{name:r,error:n instanceof Error?n.message:String(n)});}return e}var H={version:"0.3.3"};function I(){return H.version}var p=Log.create({service:"mcp.client"}),E=6e4,x=class{constructor(){a(this,"id","");a(this,"client",null);a(this,"_info");a(this,"notificationCleanup",null);a(this,"pendingTransport",null);a(this,"oauthState","");a(this,"capturedRedirectUrl",null);a(this,"oauthConfig");a(this,"toolListChangedHandler",null);a(this,"oauthProvider",null);this._info=this.createInitialInfo();}get info(){return {...this._info}}get status(){return this._info.status}get tools(){return [...this._info.tools]}get resources(){return [...this._info.resources]}get prompts(){return [...this._info.prompts]}get clientId(){return this.id}setToolListChangedHandler(t){this.toolListChangedHandler=t;}setOAuthProvider(t){this.oauthProvider=t;}async connect(t,e){p.info("connecting to MCP server",{id:t,type:e.type}),await this.disconnect(),this.id=t,this._info=this.createInitialInfo(),this._info.id=t,this._info.name=t;try{if(e.type==="remote"&&(this.oauthConfig=e.oauth===!1?void 0:e.oauth??{}),this.client=await this.createClient(e),!this.client)return;this._info.status={status:"connected"},this._info.connectedAt=Date.now(),await this.fetchCapabilities(),p.info("connected to MCP server",{id:t,toolCount:this._info.tools.length});}catch(r){let o=r instanceof Error?r.message:String(r);this._info.status={status:"failed",error:o},this._info.error=o,p.error("failed to connect to MCP server",{id:t,error:o});}}async startAuth(){if(!this.pendingTransport)throw new Error("No pending OAuth flow. Please connect to a server that requires authentication first.");return this.oauthState=Array.from(crypto.getRandomValues(new Uint8Array(32))).map(t=>t.toString(16).padStart(2,"0")).join(""),this.oauthProvider&&await this.oauthProvider.saveState(this.oauthState),{authorizationUrl:this.capturedRedirectUrl?.toString()??"",state:this.oauthState}}async finishAuth(t){if(!this.pendingTransport)throw new Error("No pending OAuth flow.");try{await this.pendingTransport.finishAuth?.(t);}catch(e){throw p.error("failed to finish OAuth",{id:this.id,error:e}),e}this.pendingTransport=null,this.capturedRedirectUrl=null,p.info("OAuth completed, ready to reconnect",{id:this.id});}isAuthRequired(){return this._info.status.status==="needs_auth"}isClientRegistrationRequired(){return this._info.status.status==="needs_client_registration"}getPendingTransport(){return this.pendingTransport}getAuthorizationUrl(){return this.capturedRedirectUrl?.toString()??""}async disconnect(){this.notificationCleanup&&(this.notificationCleanup(),this.notificationCleanup=null),this.client&&(await this.client.close().catch(t=>{p.error("Failed to close MCP client",{id:this.id,error:t});}),this.client=null),this.pendingTransport=null,this.capturedRedirectUrl=null,this.toolListChangedHandler=null,this._info.status.status==="connected"&&(this._info.status={status:"disconnected"}),p.info("disconnected from MCP server",{id:this.id});}async listTools(){if(!this.client||this._info.status.status!=="connected")return {};let t={};for(let e of this._info.tools)t[e.name]={name:e.name,description:e.description,inputSchema:e.inputSchema};return t}async callTool(t,e,r){let o=Date.now();if(!this.client||this._info.status.status!=="connected")return {toolName:t,args:e,content:[],success:false,error:"Client not connected",duration:Date.now()-o};try{let n=await withTimeout(this.client.callTool({name:t,arguments:e},CallToolResultSchema,{resetTimeoutOnProgress:!0,timeout:r}),r??E);return {toolName:t,args:e,content:n.content.map(h=>h.type==="text"?{type:"text",text:h.text}:h.type==="image"?{type:"image",data:h.data,mimeType:h.mimeType}:h.type==="resource"?{type:"resource",resource:h.resource}:{type:"text",text:String(h)}),success:!0,duration:Date.now()-o,metadata:n.metadata}}catch(n){return {toolName:t,args:e,content:[],success:false,error:n instanceof Error?n.message:String(n),duration:Date.now()-o}}}async listResources(){return [...this._info.resources]}async readResource(t){if(!this.client||this._info.status.status!=="connected"){p.warn("client not connected for readResource",{id:this.id});return}return this.client.readResource({uri:t}).catch(e=>{p.error("failed to read resource",{id:this.id,uri:t,error:e.message});})}async listPrompts(){return [...this._info.prompts]}async getPrompt(t,e){if(!this.client||this._info.status.status!=="connected"){p.warn("client not connected for getPrompt",{id:this.id});return}return this.client.getPrompt({name:t,arguments:e}).catch(r=>{p.error("failed to get prompt",{id:this.id,name:t,error:r.message});})}async refreshTools(){if(!(!this.client||this._info.status.status!=="connected"))try{let t=await withTimeout(this.client.listTools(),E);this._info.tools=t.tools.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})),p.info("tools refreshed",{id:this.id,count:this._info.tools.length});}catch(t){p.error("failed to refresh tools",{id:this.id,error:t instanceof Error?t.message:String(t)});}}async createClient(t){return t.type==="local"?await this.createLocalClient(t):t.type==="remote"?await this.createRemoteClient(t):null}async fetchCapabilities(){if(this.client){try{let t=await withTimeout(this.client.listTools(),E);this._info.tools=t.tools.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}));}catch(t){p.debug("failed to get tools",{id:this.id,error:t instanceof Error?t.message:String(t)});}try{let t=await withTimeout(this.client.listResources(),5e3);this._info.resources=t.resources.map(e=>({name:e.name,uri:e.uri,description:e.description,mimeType:e.mimeType}));}catch{p.debug("failed to get resources",{id:this.id});}try{let t=await withTimeout(this.client.listPrompts(),5e3);this._info.prompts=t.prompts.map(e=>({name:e.name,description:e.description,arguments:e.arguments?.map(r=>({name:r.name,description:r.description,required:r.required}))}));}catch{p.debug("failed to get prompts",{id:this.id});}}}async createLocalClient(t){let[e,...r]=t.command;if(!e)return this._info.status={status:"failed",error:"Command is empty"},null;let o=new StdioClientTransport({stderr:"pipe",command:e,args:r.length>0?r:void 0,cwd:t.cwd??process.cwd(),env:Object.fromEntries(Object.entries({...process.env,...t.environment}).filter(([,n])=>n!==void 0))});o.stderr?.on("data",n=>{p.info(`mcp stderr: ${n.toString()}`,{id:this.id});});try{let n=new Client({name:"easbot-mcp",version:I()});return await withTimeout(n.connect(o),t.timeout??E),this.registerNotificationHandler(n),n}catch(n){let h=n instanceof Error?n.message:String(n);return this._info.status={status:"failed",error:h},p.error("local mcp startup failed",{id:this.id,command:[e,...r],error:h}),null}}async createRemoteClient(t){let e={headers:t.headers};if((t.transport||"streamable-http")==="sse"){p.info("using SSE transport",{id:this.id,url:t.url});let o=new SSEClientTransport(new URL(t.url),{requestInit:e});return await this.connectWithTransport(o,t)}else {p.info("using StreamableHTTP transport",{id:this.id,url:t.url});let o=new StreamableHTTPClientTransport(new URL(t.url),{requestInit:e});return await this.connectWithTransport(o,t)}}async connectWithTransport(t,e){try{let r=new Client({name:"easbot-mcp",version:I()});return await r.connect(t),this.registerNotificationHandler(r),r}catch(r){return this.handleConnectionError(r,t,e),null}}handleConnectionError(t,e,r){if(t instanceof UnauthorizedError){let o=t instanceof Error?t.message:String(t);o.includes("registration")||o.includes("client_id")?this._info.status={status:"needs_client_registration",error:"Server does not support dynamic client registration. Please provide clientId in config."}:(this._info.status={status:"needs_auth"},this.pendingTransport=e,this.capturedRedirectUrl=this.extractRedirectUrl(t));}else {let o=t instanceof Error?t.message:String(t);this._info.status={status:"failed",error:o};}p.error("remote mcp connection failed",{id:this.id,url:r.url,status:this._info.status});}extractRedirectUrl(t){let r=t.message.match(/https?:\/\/[^\s]+/);if(r)try{return new URL(r[0])}catch{return null}return null}registerNotificationHandler(t){let e=async()=>{p.info("tools list changed notification received",{id:this.id}),await this.refreshTools(),this.toolListChangedHandler&&await this.toolListChangedHandler();};t.setNotificationHandler(ToolListChangedNotificationSchema,e),this.notificationCleanup=()=>{t.removeNotificationHandler("notifications/tools/list_changed");};}createInitialInfo(){return {id:"",name:"",status:{status:"disconnected"},tools:[],resources:[],prompts:[]}}};function pt(){return new x}var ye=d.discriminatedUnion("status",[d.object({status:d.literal("connected")}).meta({ref:"MCPStatusConnected"}),d.object({status:d.literal("disconnected")}).meta({ref:"MCPStatusDisconnected"}),d.object({status:d.literal("failed"),error:d.string()}).meta({ref:"MCPStatusFailed"}),d.object({status:d.literal("needs_auth")}).meta({ref:"MCPStatusNeedsAuth"}),d.object({status:d.literal("needs_client_registration"),error:d.string()}).meta({ref:"MCPStatusNeedsClientRegistration"}),d.object({status:d.literal("disabled")}).meta({ref:"MCPStatusDisabled"})]);var Pe="@easbot/mcp";export{b as HttpServerAdapter,x as MCPClient,Pe as NAME,l as ServerStatus,P as ServerTransportType,ye as Status,R as StdioServerAdapter,k as ToolRegistry,C as ToolRegistryEvent,J as createAndStartHttpServer,G as createAndStartStdioServer,$ as createHttpServer,pt as createMCPClient,et as createServerFromConfig,Ft as createServersFromConfigMap,q as createStdioServer,I as getVersion};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easbot/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "MCP (Model Context Protocol) integration library for EASBOT ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"ai": "^6.0.176",
|
|
45
45
|
"zod": "^4.4.3",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
|
-
"@easbot/types": "0.3.
|
|
48
|
-
"@easbot/utils": "0.3.
|
|
47
|
+
"@easbot/types": "0.3.3",
|
|
48
|
+
"@easbot/utils": "0.3.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@biomejs/biome": "^2.4.14",
|