@claude-flow/mcp 3.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agentic-flow/intelligence.json +16 -0
- package/README.md +428 -0
- package/__tests__/integration.test.ts +449 -0
- package/__tests__/mcp.test.ts +641 -0
- package/dist/connection-pool.d.ts +36 -0
- package/dist/connection-pool.d.ts.map +1 -0
- package/dist/connection-pool.js +273 -0
- package/dist/connection-pool.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +146 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +318 -0
- package/dist/oauth.js.map +1 -0
- package/dist/prompt-registry.d.ts +90 -0
- package/dist/prompt-registry.d.ts.map +1 -0
- package/dist/prompt-registry.js +209 -0
- package/dist/prompt-registry.js.map +1 -0
- package/dist/rate-limiter.d.ts +86 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +197 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/resource-registry.d.ts +144 -0
- package/dist/resource-registry.d.ts.map +1 -0
- package/dist/resource-registry.js +405 -0
- package/dist/resource-registry.js.map +1 -0
- package/dist/sampling.d.ts +102 -0
- package/dist/sampling.d.ts.map +1 -0
- package/dist/sampling.js +268 -0
- package/dist/sampling.js.map +1 -0
- package/dist/schema-validator.d.ts +30 -0
- package/dist/schema-validator.d.ts.map +1 -0
- package/dist/schema-validator.js +182 -0
- package/dist/schema-validator.js.map +1 -0
- package/dist/server.d.ts +122 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +829 -0
- package/dist/server.js.map +1 -0
- package/dist/session-manager.d.ts +55 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +252 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/task-manager.d.ts +81 -0
- package/dist/task-manager.d.ts.map +1 -0
- package/dist/task-manager.js +337 -0
- package/dist/task-manager.js.map +1 -0
- package/dist/tool-registry.d.ts +88 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +353 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/transport/http.d.ts +55 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +446 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/index.d.ts +50 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +181 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/stdio.d.ts +43 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +194 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport/websocket.d.ts +65 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +314 -0
- package/dist/transport/websocket.js.map +1 -0
- package/dist/types.d.ts +473 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +40 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -0
- package/src/connection-pool.ts +344 -0
- package/src/index.ts +253 -0
- package/src/oauth.ts +447 -0
- package/src/prompt-registry.ts +296 -0
- package/src/rate-limiter.ts +266 -0
- package/src/resource-registry.ts +530 -0
- package/src/sampling.ts +363 -0
- package/src/schema-validator.ts +213 -0
- package/src/server.ts +1134 -0
- package/src/session-manager.ts +339 -0
- package/src/task-manager.ts +427 -0
- package/src/tool-registry.ts +475 -0
- package/src/transport/http.ts +532 -0
- package/src/transport/index.ts +233 -0
- package/src/transport/stdio.ts +252 -0
- package/src/transport/websocket.ts +396 -0
- package/src/types.ts +664 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# @claude-flow/mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@claude-flow/mcp)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://modelcontextprotocol.io)
|
|
7
|
+
[](https://github.com/ruvnet/claude-flow)
|
|
8
|
+
|
|
9
|
+
> **MCP 2025-11-25 Compliant** - Standalone Model Context Protocol server implementation with full Resources, Prompts, and Tasks support
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **MCP 2025-11-25 Compliant** - Full specification support
|
|
14
|
+
- **High-Performance Server** - <400ms startup time
|
|
15
|
+
- **Multiple Transports** - stdio, HTTP, WebSocket, in-process
|
|
16
|
+
- **Resources** - list, read, subscribe with caching
|
|
17
|
+
- **Prompts** - templates with arguments and embedded resources
|
|
18
|
+
- **Tasks** - Async operations with progress and cancellation
|
|
19
|
+
- **Pagination** - Cursor-based pagination for large datasets
|
|
20
|
+
- **Connection Pooling** - Max 10 connections, configurable
|
|
21
|
+
- **Fast Tool Registry** - O(1) lookup, <10ms registration
|
|
22
|
+
- **Session Management** - Timeout handling, authentication
|
|
23
|
+
- **Security** - CORS, Helmet, auth tokens
|
|
24
|
+
- **Zero Dependencies** - No @claude-flow/* dependencies
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @claude-flow/mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { quickStart, defineTool } from '@claude-flow/mcp';
|
|
36
|
+
|
|
37
|
+
// Create and start server
|
|
38
|
+
const server = await quickStart({
|
|
39
|
+
transport: 'stdio',
|
|
40
|
+
name: 'My MCP Server',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Register custom tools
|
|
44
|
+
server.registerTool(defineTool(
|
|
45
|
+
'greet',
|
|
46
|
+
'Greet a user',
|
|
47
|
+
{
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
name: { type: 'string', description: 'User name' }
|
|
51
|
+
},
|
|
52
|
+
required: ['name']
|
|
53
|
+
},
|
|
54
|
+
async ({ name }) => ({ message: `Hello, ${name}!` })
|
|
55
|
+
));
|
|
56
|
+
|
|
57
|
+
// Start listening
|
|
58
|
+
await server.start();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Transports
|
|
62
|
+
|
|
63
|
+
### Stdio (Default)
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { createMCPServer } from '@claude-flow/mcp';
|
|
67
|
+
|
|
68
|
+
const server = createMCPServer({
|
|
69
|
+
transport: 'stdio',
|
|
70
|
+
name: 'Stdio Server',
|
|
71
|
+
}, logger);
|
|
72
|
+
|
|
73
|
+
await server.start();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### HTTP
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const server = createMCPServer({
|
|
80
|
+
transport: 'http',
|
|
81
|
+
host: 'localhost',
|
|
82
|
+
port: 3000,
|
|
83
|
+
corsEnabled: true,
|
|
84
|
+
corsOrigins: ['http://localhost:8080'],
|
|
85
|
+
auth: {
|
|
86
|
+
enabled: true,
|
|
87
|
+
method: 'token',
|
|
88
|
+
tokens: ['secret-token'],
|
|
89
|
+
},
|
|
90
|
+
}, logger);
|
|
91
|
+
|
|
92
|
+
await server.start();
|
|
93
|
+
// Server at http://localhost:3000
|
|
94
|
+
// WebSocket at ws://localhost:3000/ws
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### WebSocket
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const server = createMCPServer({
|
|
101
|
+
transport: 'websocket',
|
|
102
|
+
host: 'localhost',
|
|
103
|
+
port: 3001,
|
|
104
|
+
maxConnections: 100,
|
|
105
|
+
heartbeatInterval: 30000,
|
|
106
|
+
}, logger);
|
|
107
|
+
|
|
108
|
+
await server.start();
|
|
109
|
+
// WebSocket at ws://localhost:3001/ws
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Tool Registry
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { createToolRegistry, defineTool } from '@claude-flow/mcp';
|
|
116
|
+
|
|
117
|
+
const registry = createToolRegistry(logger);
|
|
118
|
+
|
|
119
|
+
// Register tool
|
|
120
|
+
registry.register({
|
|
121
|
+
name: 'calculate',
|
|
122
|
+
description: 'Perform calculations',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
expression: { type: 'string' }
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
handler: async ({ expression }) => {
|
|
130
|
+
return { result: eval(expression) };
|
|
131
|
+
},
|
|
132
|
+
category: 'math',
|
|
133
|
+
tags: ['calculator'],
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Execute tool
|
|
137
|
+
const result = await registry.execute('calculate', { expression: '2 + 2' });
|
|
138
|
+
|
|
139
|
+
// Search tools
|
|
140
|
+
const mathTools = registry.getByCategory('math');
|
|
141
|
+
const stats = registry.getStats();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Session Management
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { createSessionManager } from '@claude-flow/mcp';
|
|
148
|
+
|
|
149
|
+
const sessions = createSessionManager(logger, {
|
|
150
|
+
maxSessions: 100,
|
|
151
|
+
sessionTimeout: 30 * 60 * 1000, // 30 minutes
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Create session
|
|
155
|
+
const session = sessions.createSession('http');
|
|
156
|
+
|
|
157
|
+
// Initialize with client info
|
|
158
|
+
sessions.initializeSession(session.id, {
|
|
159
|
+
protocolVersion: { major: 2024, minor: 11, patch: 5 },
|
|
160
|
+
capabilities: { tools: { listChanged: true } },
|
|
161
|
+
clientInfo: { name: 'Claude', version: '1.0' },
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Get metrics
|
|
165
|
+
const metrics = sessions.getSessionMetrics();
|
|
166
|
+
// { total: 1, active: 1, authenticated: 0, expired: 0 }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Connection Pool
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { createConnectionPool } from '@claude-flow/mcp';
|
|
173
|
+
|
|
174
|
+
const pool = createConnectionPool({
|
|
175
|
+
maxConnections: 10,
|
|
176
|
+
minConnections: 2,
|
|
177
|
+
idleTimeout: 30000,
|
|
178
|
+
acquireTimeout: 5000,
|
|
179
|
+
}, logger, 'http');
|
|
180
|
+
|
|
181
|
+
// Acquire connection
|
|
182
|
+
const conn = await pool.acquire();
|
|
183
|
+
|
|
184
|
+
// Use connection...
|
|
185
|
+
|
|
186
|
+
// Release back to pool
|
|
187
|
+
pool.release(conn);
|
|
188
|
+
|
|
189
|
+
// Get stats
|
|
190
|
+
const stats = pool.getStats();
|
|
191
|
+
// { totalConnections: 5, idleConnections: 4, busyConnections: 1, ... }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## API Reference
|
|
195
|
+
|
|
196
|
+
### Server
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
interface IMCPServer {
|
|
200
|
+
start(): Promise<void>;
|
|
201
|
+
stop(): Promise<void>;
|
|
202
|
+
registerTool(tool: MCPTool): boolean;
|
|
203
|
+
registerTools(tools: MCPTool[]): { registered: number; failed: string[] };
|
|
204
|
+
getHealthStatus(): Promise<{ healthy: boolean; error?: string; metrics?: Record<string, number> }>;
|
|
205
|
+
getMetrics(): MCPServerMetrics;
|
|
206
|
+
getSessions(): MCPSession[];
|
|
207
|
+
getSession(sessionId: string): MCPSession | undefined;
|
|
208
|
+
terminateSession(sessionId: string): boolean;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Tool
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
interface MCPTool<TInput = unknown, TOutput = unknown> {
|
|
216
|
+
name: string;
|
|
217
|
+
description: string;
|
|
218
|
+
inputSchema: JSONSchema;
|
|
219
|
+
handler: (input: TInput, context?: ToolContext) => Promise<TOutput>;
|
|
220
|
+
category?: string;
|
|
221
|
+
tags?: string[];
|
|
222
|
+
cacheable?: boolean;
|
|
223
|
+
cacheTTL?: number;
|
|
224
|
+
timeout?: number;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Transport
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
interface ITransport {
|
|
232
|
+
readonly type: TransportType;
|
|
233
|
+
start(): Promise<void>;
|
|
234
|
+
stop(): Promise<void>;
|
|
235
|
+
onRequest(handler: RequestHandler): void;
|
|
236
|
+
onNotification(handler: NotificationHandler): void;
|
|
237
|
+
sendNotification?(notification: MCPNotification): Promise<void>;
|
|
238
|
+
getHealthStatus(): Promise<TransportHealthStatus>;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Built-in Tools
|
|
243
|
+
|
|
244
|
+
The server automatically registers these system tools:
|
|
245
|
+
|
|
246
|
+
| Tool | Description |
|
|
247
|
+
|------|-------------|
|
|
248
|
+
| `system/info` | Get server information |
|
|
249
|
+
| `system/health` | Get health status |
|
|
250
|
+
| `system/metrics` | Get server metrics |
|
|
251
|
+
| `tools/list-detailed` | List all tools with details |
|
|
252
|
+
|
|
253
|
+
## Resources (MCP 2025-11-25)
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { createMCPServer, createTextResource } from '@claude-flow/mcp';
|
|
257
|
+
|
|
258
|
+
const server = createMCPServer({ transport: 'stdio' }, logger);
|
|
259
|
+
const resourceRegistry = server.getResourceRegistry();
|
|
260
|
+
|
|
261
|
+
// Register a text resource
|
|
262
|
+
const { resource, handler } = createTextResource(
|
|
263
|
+
'file://readme.txt',
|
|
264
|
+
'README',
|
|
265
|
+
'Welcome to the application!',
|
|
266
|
+
{ mimeType: 'text/plain' }
|
|
267
|
+
);
|
|
268
|
+
resourceRegistry.registerResource(resource, handler);
|
|
269
|
+
|
|
270
|
+
// Register with custom handler
|
|
271
|
+
resourceRegistry.registerResource(
|
|
272
|
+
{
|
|
273
|
+
uri: 'db://users/{id}',
|
|
274
|
+
name: 'User Data',
|
|
275
|
+
description: 'Get user by ID',
|
|
276
|
+
mimeType: 'application/json',
|
|
277
|
+
},
|
|
278
|
+
async (uri) => {
|
|
279
|
+
const id = uri.split('/').pop();
|
|
280
|
+
const user = await fetchUser(id);
|
|
281
|
+
return [{ uri, mimeType: 'application/json', text: JSON.stringify(user) }];
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Subscribe to updates
|
|
286
|
+
resourceRegistry.subscribe('file://readme.txt', (uri, content) => {
|
|
287
|
+
console.log('Resource updated:', uri);
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Prompts (MCP 2025-11-25)
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { createMCPServer, definePrompt, textMessage, interpolate } from '@claude-flow/mcp';
|
|
295
|
+
|
|
296
|
+
const server = createMCPServer({ transport: 'stdio' }, logger);
|
|
297
|
+
const promptRegistry = server.getPromptRegistry();
|
|
298
|
+
|
|
299
|
+
// Define a prompt with arguments
|
|
300
|
+
const codeReviewPrompt = definePrompt(
|
|
301
|
+
'code_review',
|
|
302
|
+
'Review code for quality and best practices',
|
|
303
|
+
async (args) => [
|
|
304
|
+
textMessage('user', `Please review this ${args.language} code:\n\n${args.code}`),
|
|
305
|
+
],
|
|
306
|
+
{
|
|
307
|
+
title: 'Code Review',
|
|
308
|
+
arguments: [
|
|
309
|
+
{ name: 'code', description: 'The code to review', required: true },
|
|
310
|
+
{ name: 'language', description: 'Programming language', required: false },
|
|
311
|
+
],
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
promptRegistry.register(codeReviewPrompt);
|
|
316
|
+
|
|
317
|
+
// Get prompt with arguments
|
|
318
|
+
const result = await promptRegistry.get('code_review', {
|
|
319
|
+
code: 'function hello() { return "world"; }',
|
|
320
|
+
language: 'JavaScript',
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Tasks (MCP 2025-11-25)
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { createMCPServer } from '@claude-flow/mcp';
|
|
328
|
+
|
|
329
|
+
const server = createMCPServer({ transport: 'stdio' }, logger);
|
|
330
|
+
const taskManager = server.getTaskManager();
|
|
331
|
+
|
|
332
|
+
// Create a long-running task
|
|
333
|
+
const taskId = taskManager.createTask(async (reportProgress, signal) => {
|
|
334
|
+
for (let i = 0; i <= 100; i += 10) {
|
|
335
|
+
if (signal.aborted) throw new Error('Task cancelled');
|
|
336
|
+
|
|
337
|
+
reportProgress({
|
|
338
|
+
progress: i,
|
|
339
|
+
total: 100,
|
|
340
|
+
message: `Processing... ${i}%`,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
await new Promise(r => setTimeout(r, 100));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { success: true, processedItems: 100 };
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Get task status
|
|
350
|
+
const status = taskManager.getTask(taskId);
|
|
351
|
+
|
|
352
|
+
// Wait for completion
|
|
353
|
+
const result = await taskManager.waitForTask(taskId);
|
|
354
|
+
|
|
355
|
+
// Cancel a task
|
|
356
|
+
taskManager.cancelTask(taskId, 'User requested cancellation');
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## MCP Methods Supported
|
|
360
|
+
|
|
361
|
+
| Method | Description |
|
|
362
|
+
|--------|-------------|
|
|
363
|
+
| `initialize` | Initialize connection |
|
|
364
|
+
| `tools/list` | List available tools |
|
|
365
|
+
| `tools/call` | Execute a tool |
|
|
366
|
+
| `resources/list` | List resources with pagination |
|
|
367
|
+
| `resources/read` | Read resource content |
|
|
368
|
+
| `resources/subscribe` | Subscribe to updates |
|
|
369
|
+
| `resources/unsubscribe` | Unsubscribe from updates |
|
|
370
|
+
| `prompts/list` | List prompts with pagination |
|
|
371
|
+
| `prompts/get` | Get prompt with arguments |
|
|
372
|
+
| `tasks/status` | Get task status |
|
|
373
|
+
| `tasks/cancel` | Cancel a running task |
|
|
374
|
+
| `completion/complete` | Auto-complete arguments |
|
|
375
|
+
| `logging/setLevel` | Set log level |
|
|
376
|
+
| `ping` | Keep-alive |
|
|
377
|
+
|
|
378
|
+
## Performance Targets
|
|
379
|
+
|
|
380
|
+
| Metric | Target |
|
|
381
|
+
|--------|--------|
|
|
382
|
+
| Server startup | <400ms |
|
|
383
|
+
| Tool registration | <10ms |
|
|
384
|
+
| Tool execution overhead | <50ms |
|
|
385
|
+
| Connection acquire | <5ms |
|
|
386
|
+
| Connection release | <1ms |
|
|
387
|
+
|
|
388
|
+
## Security
|
|
389
|
+
|
|
390
|
+
### Authentication
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
const server = createMCPServer({
|
|
394
|
+
auth: {
|
|
395
|
+
enabled: true,
|
|
396
|
+
method: 'token',
|
|
397
|
+
tokens: ['your-secret-token'],
|
|
398
|
+
},
|
|
399
|
+
}, logger);
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### CORS
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
const server = createMCPServer({
|
|
406
|
+
transport: 'http',
|
|
407
|
+
corsEnabled: true,
|
|
408
|
+
corsOrigins: ['https://your-domain.com'],
|
|
409
|
+
}, logger);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Error Codes
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
const ErrorCodes = {
|
|
416
|
+
PARSE_ERROR: -32700,
|
|
417
|
+
INVALID_REQUEST: -32600,
|
|
418
|
+
METHOD_NOT_FOUND: -32601,
|
|
419
|
+
INVALID_PARAMS: -32602,
|
|
420
|
+
INTERNAL_ERROR: -32603,
|
|
421
|
+
SERVER_NOT_INITIALIZED: -32002,
|
|
422
|
+
AUTHENTICATION_REQUIRED: -32001,
|
|
423
|
+
};
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## License
|
|
427
|
+
|
|
428
|
+
MIT
|