toolception 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +902 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +737 -262
- package/dist/index.js.map +1 -1
- package/dist/permissions/PermissionAwareFastifyTransport.d.ts +46 -0
- package/dist/permissions/PermissionAwareFastifyTransport.d.ts.map +1 -0
- package/dist/permissions/PermissionResolver.d.ts +31 -0
- package/dist/permissions/PermissionResolver.d.ts.map +1 -0
- package/dist/permissions/createPermissionAwareBundle.d.ts +52 -0
- package/dist/permissions/createPermissionAwareBundle.d.ts.map +1 -0
- package/dist/permissions/validatePermissionConfig.d.ts +9 -0
- package/dist/permissions/validatePermissionConfig.d.ts.map +1 -0
- package/dist/server/createPermissionBasedMcpServer.d.ts +65 -0
- package/dist/server/createPermissionBasedMcpServer.d.ts.map +1 -0
- package/dist/types/index.d.ts +229 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { CreatePermissionBasedMcpServerOptions } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates an MCP server with permission-based toolset access control.
|
|
5
|
+
*
|
|
6
|
+
* This function provides a separate API for creating servers where each client receives
|
|
7
|
+
* only the toolsets they're authorized to access. Each client gets a fresh server instance
|
|
8
|
+
* with STATIC mode configured to their allowed toolsets, ensuring per-client isolation
|
|
9
|
+
* without meta-tools or dynamic loading.
|
|
10
|
+
*
|
|
11
|
+
* The server supports two permission sources:
|
|
12
|
+
* - **Header-based**: Permissions are read from request headers (e.g., `mcp-toolset-permissions`)
|
|
13
|
+
* - **Config-based**: Permissions are resolved server-side using static maps or resolver functions
|
|
14
|
+
*
|
|
15
|
+
* @param options - Configuration options including permission settings, catalog, and HTTP transport options
|
|
16
|
+
* @returns Server instance with `server`, `start()`, and `close()` methods matching the createMcpServer interface
|
|
17
|
+
* @throws {Error} If permission configuration is invalid, missing, or if startup.mode is provided
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Header-based permissions
|
|
21
|
+
* const server = await createPermissionBasedMcpServer({
|
|
22
|
+
* createServer: () => new McpServer({ name: "my-server", version: "1.0.0" }),
|
|
23
|
+
* catalog: { toolsetA: { name: "Toolset A", tools: [...] } },
|
|
24
|
+
* permissions: {
|
|
25
|
+
* source: 'headers',
|
|
26
|
+
* headerName: 'mcp-toolset-permissions' // optional, this is the default
|
|
27
|
+
* }
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Config-based permissions with static map
|
|
32
|
+
* const server = await createPermissionBasedMcpServer({
|
|
33
|
+
* createServer: () => new McpServer({ name: "my-server", version: "1.0.0" }),
|
|
34
|
+
* catalog: { toolsetA: { name: "Toolset A", tools: [...] } },
|
|
35
|
+
* permissions: {
|
|
36
|
+
* source: 'config',
|
|
37
|
+
* staticMap: {
|
|
38
|
+
* 'client-1': ['toolsetA', 'toolsetB'],
|
|
39
|
+
* 'client-2': ['toolsetC']
|
|
40
|
+
* },
|
|
41
|
+
* defaultPermissions: [] // optional, defaults to empty array
|
|
42
|
+
* }
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Config-based permissions with resolver function
|
|
47
|
+
* const server = await createPermissionBasedMcpServer({
|
|
48
|
+
* createServer: () => new McpServer({ name: "my-server", version: "1.0.0" }),
|
|
49
|
+
* catalog: { toolsetA: { name: "Toolset A", tools: [...] } },
|
|
50
|
+
* permissions: {
|
|
51
|
+
* source: 'config',
|
|
52
|
+
* resolver: (clientId) => {
|
|
53
|
+
* // Your custom logic to determine permissions
|
|
54
|
+
* return clientId.startsWith('admin-') ? ['toolsetA', 'toolsetB'] : ['toolsetA'];
|
|
55
|
+
* },
|
|
56
|
+
* defaultPermissions: ['toolsetA'] // fallback if resolver fails
|
|
57
|
+
* }
|
|
58
|
+
* });
|
|
59
|
+
*/
|
|
60
|
+
export declare function createPermissionBasedMcpServer(options: CreatePermissionBasedMcpServerOptions): Promise<{
|
|
61
|
+
server: McpServer;
|
|
62
|
+
start: () => Promise<void>;
|
|
63
|
+
close: () => Promise<void>;
|
|
64
|
+
}>;
|
|
65
|
+
//# sourceMappingURL=createPermissionBasedMcpServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createPermissionBasedMcpServer.d.ts","sourceRoot":"","sources":["../../src/server/createPermissionBasedMcpServer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,mBAAmB,CAAC;AAO/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,qCAAqC;;;;GA6F/C"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CreateMcpServerOptions } from '../server/createMcpServer.js';
|
|
1
2
|
export type McpToolDefinition = {
|
|
2
3
|
name: string;
|
|
3
4
|
description: string;
|
|
@@ -22,4 +23,232 @@ export type ExposurePolicy = {
|
|
|
22
23
|
};
|
|
23
24
|
export type ToolingErrorCode = "E_VALIDATION" | "E_POLICY_MAX_ACTIVE" | "E_TOOL_NAME_CONFLICT" | "E_NOTIFY_FAILED" | "E_INTERNAL";
|
|
24
25
|
export type ModuleLoader = (context?: unknown) => Promise<McpToolDefinition[]> | McpToolDefinition[];
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for permission-based toolset access control.
|
|
28
|
+
*
|
|
29
|
+
* Defines how client permissions are resolved and applied when using
|
|
30
|
+
* `createPermissionBasedMcpServer`. Supports two permission sources:
|
|
31
|
+
*
|
|
32
|
+
* **Header-based permissions**: Permissions are extracted from request headers.
|
|
33
|
+
* This approach is simpler but requires proper authentication/authorization in your
|
|
34
|
+
* application layer to prevent header tampering.
|
|
35
|
+
*
|
|
36
|
+
* **Config-based permissions**: Permissions are resolved server-side using either
|
|
37
|
+
* a static map or a resolver function. This provides stronger security by not
|
|
38
|
+
* trusting client-provided permission data.
|
|
39
|
+
*
|
|
40
|
+
* @example Header-based configuration
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const config: PermissionConfig = {
|
|
43
|
+
* source: 'headers',
|
|
44
|
+
* headerName: 'mcp-toolset-permissions' // optional, this is the default
|
|
45
|
+
* };
|
|
46
|
+
* // Client sends: mcp-toolset-permissions: toolset-a,toolset-b
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example Config-based with static map
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const config: PermissionConfig = {
|
|
52
|
+
* source: 'config',
|
|
53
|
+
* staticMap: {
|
|
54
|
+
* 'client-1': ['toolset-a', 'toolset-b'],
|
|
55
|
+
* 'client-2': ['toolset-c']
|
|
56
|
+
* },
|
|
57
|
+
* defaultPermissions: [] // optional, for unknown clients
|
|
58
|
+
* };
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @example Config-based with resolver function
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const config: PermissionConfig = {
|
|
64
|
+
* source: 'config',
|
|
65
|
+
* resolver: (clientId: string) => {
|
|
66
|
+
* // Custom logic to determine permissions
|
|
67
|
+
* if (clientId.startsWith('admin-')) {
|
|
68
|
+
* return ['toolset-a', 'toolset-b', 'toolset-c'];
|
|
69
|
+
* }
|
|
70
|
+
* return ['toolset-a'];
|
|
71
|
+
* },
|
|
72
|
+
* defaultPermissions: ['toolset-a'] // fallback if resolver fails
|
|
73
|
+
* };
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export type PermissionConfig = {
|
|
77
|
+
/**
|
|
78
|
+
* The source of permission data.
|
|
79
|
+
*
|
|
80
|
+
* - `'headers'`: Read permissions from request headers. Requires proper authentication
|
|
81
|
+
* in your application layer to prevent tampering.
|
|
82
|
+
* - `'config'`: Use server-side permission configuration via staticMap or resolver.
|
|
83
|
+
* Provides stronger security by not trusting client-provided data.
|
|
84
|
+
*/
|
|
85
|
+
source: "headers" | "config";
|
|
86
|
+
/**
|
|
87
|
+
* Name of the header containing permission data (for header-based permissions).
|
|
88
|
+
*
|
|
89
|
+
* The header value should be a comma-separated list of toolset names.
|
|
90
|
+
* Only used when `source` is `'headers'`.
|
|
91
|
+
*
|
|
92
|
+
* @default 'mcp-toolset-permissions'
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* // With default header name
|
|
96
|
+
* { source: 'headers' }
|
|
97
|
+
* // Client sends: mcp-toolset-permissions: toolset-a,toolset-b
|
|
98
|
+
*
|
|
99
|
+
* // With custom header name
|
|
100
|
+
* { source: 'headers', headerName: 'x-allowed-toolsets' }
|
|
101
|
+
* // Client sends: x-allowed-toolsets: toolset-a,toolset-b
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
headerName?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Static mapping of client IDs to their allowed toolsets (for config-based permissions).
|
|
107
|
+
*
|
|
108
|
+
* Each key is a client ID, and the value is an array of toolset names the client can access.
|
|
109
|
+
* Only used when `source` is `'config'`. At least one of `staticMap` or `resolver` must
|
|
110
|
+
* be provided for config-based permissions.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* {
|
|
115
|
+
* source: 'config',
|
|
116
|
+
* staticMap: {
|
|
117
|
+
* 'client-1': ['toolset-a', 'toolset-b'],
|
|
118
|
+
* 'client-2': ['toolset-c'],
|
|
119
|
+
* 'admin-user': ['toolset-a', 'toolset-b', 'toolset-c']
|
|
120
|
+
* }
|
|
121
|
+
* }
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
staticMap?: Record<string, string[]>;
|
|
125
|
+
/**
|
|
126
|
+
* Synchronous function to resolve permissions for a client (for config-based permissions).
|
|
127
|
+
*
|
|
128
|
+
* Called with the client ID and should return an array of allowed toolset names.
|
|
129
|
+
* Only used when `source` is `'config'`. If both `staticMap` and `resolver` are provided,
|
|
130
|
+
* the resolver takes precedence with staticMap as fallback.
|
|
131
|
+
*
|
|
132
|
+
* The function should be synchronous and deterministic. If it throws an error or returns
|
|
133
|
+
* a non-array value, the system falls back to `staticMap` or `defaultPermissions`.
|
|
134
|
+
*
|
|
135
|
+
* @param clientId - The unique identifier for the client requesting access
|
|
136
|
+
* @returns Array of toolset names the client is allowed to access
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* {
|
|
141
|
+
* source: 'config',
|
|
142
|
+
* resolver: (clientId: string) => {
|
|
143
|
+
* // Integrate with your auth system
|
|
144
|
+
* const user = getUserFromCache(clientId);
|
|
145
|
+
* if (user.role === 'admin') {
|
|
146
|
+
* return ['toolset-a', 'toolset-b', 'toolset-c'];
|
|
147
|
+
* } else if (user.role === 'user') {
|
|
148
|
+
* return ['toolset-a'];
|
|
149
|
+
* }
|
|
150
|
+
* return [];
|
|
151
|
+
* }
|
|
152
|
+
* }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
resolver?: (clientId: string) => string[];
|
|
156
|
+
/**
|
|
157
|
+
* Default permissions to apply when a client is not found in staticMap or resolver fails.
|
|
158
|
+
*
|
|
159
|
+
* Used as a fallback when:
|
|
160
|
+
* - Client ID is not found in `staticMap`
|
|
161
|
+
* - `resolver` function throws an error or returns invalid data
|
|
162
|
+
* - No other permission source provides valid permissions
|
|
163
|
+
*
|
|
164
|
+
* If not specified, defaults to an empty array (no toolsets allowed).
|
|
165
|
+
*
|
|
166
|
+
* @default []
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* {
|
|
170
|
+
* source: 'config',
|
|
171
|
+
* staticMap: { 'known-client': ['toolset-a'] },
|
|
172
|
+
* defaultPermissions: ['public-toolset'] // Unknown clients get this
|
|
173
|
+
* }
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
defaultPermissions?: string[];
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Options for creating a permission-based MCP server.
|
|
180
|
+
*
|
|
181
|
+
* Extends `CreateMcpServerOptions` but removes the `startup` field and requires
|
|
182
|
+
* a `permissions` configuration. Permission-based servers automatically use DYNAMIC
|
|
183
|
+
* mode internally to ensure per-client isolation, with each client receiving only
|
|
184
|
+
* their authorized toolsets.
|
|
185
|
+
*
|
|
186
|
+
* All standard options from `CreateMcpServerOptions` are supported, including:
|
|
187
|
+
* - `createServer`: Factory function to create MCP server instances
|
|
188
|
+
* - `catalog`: Toolset catalog defining available toolsets
|
|
189
|
+
* - `moduleLoaders`: Optional lazy-loading modules for toolsets
|
|
190
|
+
* - `exposurePolicy`: Optional policy for toolset exposure control
|
|
191
|
+
* - `http`: HTTP transport configuration (host, port, CORS, etc.)
|
|
192
|
+
* - `context`: Optional context passed to module loaders
|
|
193
|
+
*
|
|
194
|
+
* @example Basic header-based server
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const options: CreatePermissionBasedMcpServerOptions = {
|
|
197
|
+
* createServer: () => new McpServer({ name: "my-server", version: "1.0.0" }),
|
|
198
|
+
* catalog: {
|
|
199
|
+
* 'toolset-a': { name: 'Toolset A', tools: [...] },
|
|
200
|
+
* 'toolset-b': { name: 'Toolset B', tools: [...] }
|
|
201
|
+
* },
|
|
202
|
+
* permissions: {
|
|
203
|
+
* source: 'headers'
|
|
204
|
+
* },
|
|
205
|
+
* http: {
|
|
206
|
+
* port: 3000,
|
|
207
|
+
* cors: true
|
|
208
|
+
* }
|
|
209
|
+
* };
|
|
210
|
+
* ```
|
|
211
|
+
*
|
|
212
|
+
* @example Config-based server with static map
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const options: CreatePermissionBasedMcpServerOptions = {
|
|
215
|
+
* createServer: () => new McpServer({ name: "my-server", version: "1.0.0" }),
|
|
216
|
+
* catalog: {
|
|
217
|
+
* 'toolset-a': { name: 'Toolset A', tools: [...] },
|
|
218
|
+
* 'toolset-b': { name: 'Toolset B', tools: [...] }
|
|
219
|
+
* },
|
|
220
|
+
* permissions: {
|
|
221
|
+
* source: 'config',
|
|
222
|
+
* staticMap: {
|
|
223
|
+
* 'client-1': ['toolset-a'],
|
|
224
|
+
* 'client-2': ['toolset-a', 'toolset-b']
|
|
225
|
+
* },
|
|
226
|
+
* defaultPermissions: []
|
|
227
|
+
* }
|
|
228
|
+
* };
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export type CreatePermissionBasedMcpServerOptions = Omit<CreateMcpServerOptions, "startup"> & {
|
|
232
|
+
/**
|
|
233
|
+
* Permission configuration defining how client access control is enforced.
|
|
234
|
+
*
|
|
235
|
+
* This field is required for permission-based servers. It determines whether
|
|
236
|
+
* permissions are read from request headers or resolved server-side using
|
|
237
|
+
* static maps or resolver functions.
|
|
238
|
+
*
|
|
239
|
+
* @see {@link PermissionConfig} for detailed configuration options and examples
|
|
240
|
+
*/
|
|
241
|
+
permissions: PermissionConfig;
|
|
242
|
+
/**
|
|
243
|
+
* Startup configuration is not allowed for permission-based servers.
|
|
244
|
+
*
|
|
245
|
+
* Permission-based servers automatically determine which toolsets to load for
|
|
246
|
+
* each client based on the `permissions` configuration. The server internally
|
|
247
|
+
* uses STATIC mode per client to ensure isolation and prevent dynamic toolset
|
|
248
|
+
* changes during a session.
|
|
249
|
+
*
|
|
250
|
+
* @deprecated Do not use - permission-based servers determine toolsets from client permissions
|
|
251
|
+
*/
|
|
252
|
+
startup?: never;
|
|
253
|
+
};
|
|
25
254
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAI3E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAE5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAE/D,MAAM,MAAM,IAAI,GAAG,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEhD,MAAM,MAAM,cAAc,GAAG;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,qBAAqB,GACrB,sBAAsB,GACtB,iBAAiB,GACjB,YAAY,CAAC;AAKjB,MAAM,MAAM,YAAY,GAAG,CACzB,OAAO,CAAC,EAAE,OAAO,KACd,OAAO,CAAC,iBAAiB,EAAE,CAAC,GAAG,iBAAiB,EAAE,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;;OAOG;IACH,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAE7B;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IAE1C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,MAAM,MAAM,qCAAqC,GAAG,IAAI,CACtD,sBAAsB,EACtB,SAAS,CACV,GAAG;IACF;;;;;;;;OAQG;IACH,WAAW,EAAE,gBAAgB,CAAC;IAE9B;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolception",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -58,6 +58,11 @@
|
|
|
58
58
|
"dynamic-tools",
|
|
59
59
|
"toolset",
|
|
60
60
|
"tool-management",
|
|
61
|
+
"permissions",
|
|
62
|
+
"authentication",
|
|
63
|
+
"access-control",
|
|
64
|
+
"security",
|
|
65
|
+
"authorization",
|
|
61
66
|
"json-rpc",
|
|
62
67
|
"fastify",
|
|
63
68
|
"zod",
|