@nocoo/base-mcp 0.1.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/README.md +194 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth-metadata.d.ts +31 -0
- package/dist/auth/oauth-metadata.d.ts.map +1 -0
- package/dist/auth/oauth-metadata.js +33 -0
- package/dist/auth/oauth-metadata.js.map +1 -0
- package/dist/auth/origin.d.ts +32 -0
- package/dist/auth/origin.d.ts.map +1 -0
- package/dist/auth/origin.js +66 -0
- package/dist/auth/origin.js.map +1 -0
- package/dist/auth/pkce.d.ts +40 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +78 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/token.d.ts +79 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +108 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/framework/handlers.d.ts +14 -0
- package/dist/framework/handlers.d.ts.map +1 -0
- package/dist/framework/handlers.js +110 -0
- package/dist/framework/handlers.js.map +1 -0
- package/dist/framework/index.d.ts +7 -0
- package/dist/framework/index.d.ts.map +1 -0
- package/dist/framework/index.js +7 -0
- package/dist/framework/index.js.map +1 -0
- package/dist/framework/projection.d.ts +10 -0
- package/dist/framework/projection.d.ts.map +1 -0
- package/dist/framework/projection.js +21 -0
- package/dist/framework/projection.js.map +1 -0
- package/dist/framework/register.d.ts +16 -0
- package/dist/framework/register.d.ts.map +1 -0
- package/dist/framework/register.js +70 -0
- package/dist/framework/register.js.map +1 -0
- package/dist/framework/resolve.d.ts +39 -0
- package/dist/framework/resolve.d.ts.map +1 -0
- package/dist/framework/resolve.js +46 -0
- package/dist/framework/resolve.js.map +1 -0
- package/dist/framework/response.d.ts +6 -0
- package/dist/framework/response.d.ts.map +1 -0
- package/dist/framework/response.js +17 -0
- package/dist/framework/response.js.map +1 -0
- package/dist/framework/types.d.ts +85 -0
- package/dist/framework/types.d.ts.map +1 -0
- package/dist/framework/types.js +5 -0
- package/dist/framework/types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/server/create-server.d.ts +24 -0
- package/dist/server/create-server.d.ts.map +1 -0
- package/dist/server/create-server.js +24 -0
- package/dist/server/create-server.js.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/utils.d.ts +55 -0
- package/dist/testing/utils.d.ts.map +1 -0
- package/dist/testing/utils.js +66 -0
- package/dist/testing/utils.js.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# @nocoo/base-mcp
|
|
2
|
+
|
|
3
|
+
MCP Server development framework with OAuth 2.1, Entity-Driven CRUD, and Streamable HTTP transport support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Entity-Driven CRUD Framework**: Declarative entity definitions → automatic MCP tool generation
|
|
8
|
+
- **OAuth 2.1 Support**: PKCE, Dynamic Client Registration, token management
|
|
9
|
+
- **DNS Rebinding Protection**: Origin validation for HTTP transport
|
|
10
|
+
- **Testing Utilities**: Mock context, result parsing, token store mocking
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @nocoo/base-mcp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Peer dependencies:
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @modelcontextprotocol/sdk zod
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { createMcpServer, registerEntityTools, ok, error } from "@nocoo/base-mcp";
|
|
27
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
|
|
30
|
+
// 1. Create MCP Server
|
|
31
|
+
const server = createMcpServer({
|
|
32
|
+
name: "my-app",
|
|
33
|
+
version: "1.0.0",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 2. Define an Entity
|
|
37
|
+
const productEntity = {
|
|
38
|
+
name: "product",
|
|
39
|
+
display: "Product",
|
|
40
|
+
plural: "products",
|
|
41
|
+
dataLayer: {
|
|
42
|
+
list: async (ctx, opts) => ctx.repos.products.list(opts),
|
|
43
|
+
getById: async (ctx, id) => ctx.repos.products.getById(id),
|
|
44
|
+
create: async (ctx, input) => ctx.repos.products.create(input),
|
|
45
|
+
update: async (ctx, id, input) => ctx.repos.products.update(id, input),
|
|
46
|
+
delete: async (ctx, id) => ctx.repos.products.delete(id),
|
|
47
|
+
},
|
|
48
|
+
schemas: {
|
|
49
|
+
list: { category: z.string().optional() },
|
|
50
|
+
create: { name: z.string(), price: z.number() },
|
|
51
|
+
update: { name: z.string().optional(), price: z.number().optional() },
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// 3. Register Entity Tools
|
|
56
|
+
const ctx = { repos: createRepos(db) };
|
|
57
|
+
registerEntityTools(server, productEntity, ctx);
|
|
58
|
+
|
|
59
|
+
// 4. Handle HTTP requests (Hono/Cloudflare Worker example)
|
|
60
|
+
app.post("/mcp", async (c) => {
|
|
61
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
62
|
+
sessionIdGenerator: undefined, // Stateless
|
|
63
|
+
enableJsonResponse: true,
|
|
64
|
+
});
|
|
65
|
+
await server.connect(transport);
|
|
66
|
+
return transport.handleRequest(c.req.raw);
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## API Reference
|
|
71
|
+
|
|
72
|
+
### Server
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { createMcpServer } from "@nocoo/base-mcp";
|
|
76
|
+
|
|
77
|
+
const server = createMcpServer({
|
|
78
|
+
name: "my-server",
|
|
79
|
+
version: "1.0.0",
|
|
80
|
+
capabilities: { tools: true, resources: false },
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Framework
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import {
|
|
88
|
+
// Entity registration
|
|
89
|
+
registerEntityTools,
|
|
90
|
+
registerCustomTool,
|
|
91
|
+
createCrudHandlers,
|
|
92
|
+
|
|
93
|
+
// Response builders
|
|
94
|
+
ok,
|
|
95
|
+
error,
|
|
96
|
+
|
|
97
|
+
// Field projection
|
|
98
|
+
projectFields,
|
|
99
|
+
|
|
100
|
+
// ID/Slug resolution
|
|
101
|
+
validateIdOrSlug,
|
|
102
|
+
resolveEntity,
|
|
103
|
+
isResolveError,
|
|
104
|
+
} from "@nocoo/base-mcp";
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Auth
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import {
|
|
111
|
+
// PKCE
|
|
112
|
+
verifyPkceS256,
|
|
113
|
+
generateCodeVerifier,
|
|
114
|
+
generateCodeChallenge,
|
|
115
|
+
isLoopbackRedirectUri,
|
|
116
|
+
|
|
117
|
+
// OAuth Metadata
|
|
118
|
+
getOAuthMetadata,
|
|
119
|
+
|
|
120
|
+
// Origin Validation
|
|
121
|
+
validateOrigin,
|
|
122
|
+
isLoopbackHost,
|
|
123
|
+
|
|
124
|
+
// Token Management
|
|
125
|
+
generateToken,
|
|
126
|
+
hashToken,
|
|
127
|
+
tokenPreview,
|
|
128
|
+
extractBearerToken,
|
|
129
|
+
validateMcpToken,
|
|
130
|
+
} from "@nocoo/base-mcp/auth";
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Testing
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import {
|
|
137
|
+
createMockContext,
|
|
138
|
+
parseToolResult,
|
|
139
|
+
isToolError,
|
|
140
|
+
getToolErrorMessage,
|
|
141
|
+
createMockTokenStore,
|
|
142
|
+
} from "@nocoo/base-mcp/testing";
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Entity Configuration
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
interface EntityConfig<T, TRepos> {
|
|
149
|
+
name: string; // Singular name (e.g., "product")
|
|
150
|
+
display: string; // Human-readable name
|
|
151
|
+
plural: string; // Plural name for list tool
|
|
152
|
+
|
|
153
|
+
dataLayer: {
|
|
154
|
+
list: (ctx, opts) => Promise<T[]>;
|
|
155
|
+
getById: (ctx, id) => Promise<T | null>;
|
|
156
|
+
getBySlug?: (ctx, slug) => Promise<T | null>;
|
|
157
|
+
create?: (ctx, input) => Promise<T>;
|
|
158
|
+
update?: (ctx, id, input) => Promise<T | null>;
|
|
159
|
+
delete?: (ctx, id) => Promise<boolean>;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
schemas?: {
|
|
163
|
+
list?: Record<string, ZodType>;
|
|
164
|
+
create?: Record<string, ZodType>;
|
|
165
|
+
update?: Record<string, ZodType>;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
descriptions?: {
|
|
169
|
+
list?: string;
|
|
170
|
+
get?: string;
|
|
171
|
+
create?: string;
|
|
172
|
+
update?: string;
|
|
173
|
+
delete?: string;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
projection?: {
|
|
177
|
+
omit: string[];
|
|
178
|
+
groups: Record<string, string[]>;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
hooks?: {
|
|
182
|
+
beforeCreate?: (ctx, input) => Promise<Record<string, unknown>>;
|
|
183
|
+
afterCreate?: (ctx, entity) => Promise<void>;
|
|
184
|
+
beforeUpdate?: (ctx, id, input, existing) => Promise<Record<string, unknown>>;
|
|
185
|
+
afterUpdate?: (ctx, id, input, result) => Promise<void>;
|
|
186
|
+
beforeDelete?: (ctx, id, existing) => Promise<void>;
|
|
187
|
+
afterDelete?: (ctx, id) => Promise<void>;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { verifyPkceS256, generateCodeVerifier, generateCodeChallenge, isLoopbackRedirectUri, } from "./pkce.js";
|
|
2
|
+
export { getOAuthMetadata, type OAuthMetadata, type OAuthMetadataOptions, } from "./oauth-metadata.js";
|
|
3
|
+
export { validateOrigin, isLoopbackHost, type OriginValidationResult, } from "./origin.js";
|
|
4
|
+
export { generateToken, hashToken, tokenPreview, extractBearerToken, validateMcpToken, type TokenStore, type TokenValidationResult, type TokenValidationSuccess, type TokenValidationError, } from "./token.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,cAAc,EACd,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,GAC1B,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Auth exports
|
|
2
|
+
export { verifyPkceS256, generateCodeVerifier, generateCodeChallenge, isLoopbackRedirectUri, } from "./pkce.js";
|
|
3
|
+
export { getOAuthMetadata, } from "./oauth-metadata.js";
|
|
4
|
+
export { validateOrigin, isLoopbackHost, } from "./origin.js";
|
|
5
|
+
export { generateToken, hashToken, tokenPreview, extractBearerToken, validateMcpToken, } from "./token.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,eAAe;AACf,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,GAGjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,cAAc,GAEf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,GAKjB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface OAuthMetadata {
|
|
2
|
+
issuer: string;
|
|
3
|
+
authorization_endpoint: string;
|
|
4
|
+
token_endpoint: string;
|
|
5
|
+
registration_endpoint?: string;
|
|
6
|
+
response_types_supported: string[];
|
|
7
|
+
grant_types_supported: string[];
|
|
8
|
+
code_challenge_methods_supported: string[];
|
|
9
|
+
token_endpoint_auth_methods_supported: string[];
|
|
10
|
+
scopes_supported?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface OAuthMetadataOptions {
|
|
13
|
+
/** Base path for OAuth endpoints (default: "/api/mcp") */
|
|
14
|
+
basePath?: string;
|
|
15
|
+
/** Supported scopes (default: ["mcp:full"]) */
|
|
16
|
+
scopes?: string[];
|
|
17
|
+
/** Enable dynamic client registration endpoint */
|
|
18
|
+
enableRegistration?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generate OAuth 2.1 Authorization Server Metadata.
|
|
22
|
+
*
|
|
23
|
+
* This metadata is served at `/.well-known/oauth-authorization-server`
|
|
24
|
+
* and allows MCP clients to discover OAuth endpoints.
|
|
25
|
+
*
|
|
26
|
+
* @param issuer - The base URL of the authorization server (e.g., "https://example.com")
|
|
27
|
+
* @param options - Optional configuration
|
|
28
|
+
* @returns OAuth metadata object
|
|
29
|
+
*/
|
|
30
|
+
export declare function getOAuthMetadata(issuer: string, options?: OAuthMetadataOptions): OAuthMetadata;
|
|
31
|
+
//# sourceMappingURL=oauth-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-metadata.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-metadata.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gCAAgC,EAAE,MAAM,EAAE,CAAC;IAC3C,qCAAqC,EAAE,MAAM,EAAE,CAAC;IAChD,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,kDAAkD;IAClD,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,oBAAyB,GACjC,aAAa,CA0Bf"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// OAuth 2.1 Authorization Server Metadata
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
/**
|
|
5
|
+
* Generate OAuth 2.1 Authorization Server Metadata.
|
|
6
|
+
*
|
|
7
|
+
* This metadata is served at `/.well-known/oauth-authorization-server`
|
|
8
|
+
* and allows MCP clients to discover OAuth endpoints.
|
|
9
|
+
*
|
|
10
|
+
* @param issuer - The base URL of the authorization server (e.g., "https://example.com")
|
|
11
|
+
* @param options - Optional configuration
|
|
12
|
+
* @returns OAuth metadata object
|
|
13
|
+
*/
|
|
14
|
+
export function getOAuthMetadata(issuer, options = {}) {
|
|
15
|
+
const { basePath = "/api/mcp", scopes = ["mcp:full"], enableRegistration = true, } = options;
|
|
16
|
+
// Remove trailing slash from issuer
|
|
17
|
+
const base = issuer.replace(/\/$/, "");
|
|
18
|
+
const metadata = {
|
|
19
|
+
issuer: base,
|
|
20
|
+
authorization_endpoint: `${base}${basePath}/authorize`,
|
|
21
|
+
token_endpoint: `${base}${basePath}/token`,
|
|
22
|
+
response_types_supported: ["code"],
|
|
23
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
24
|
+
code_challenge_methods_supported: ["S256"],
|
|
25
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
26
|
+
scopes_supported: scopes,
|
|
27
|
+
};
|
|
28
|
+
if (enableRegistration) {
|
|
29
|
+
metadata.registration_endpoint = `${base}${basePath}/register`;
|
|
30
|
+
}
|
|
31
|
+
return metadata;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=oauth-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-metadata.js","sourceRoot":"","sources":["../../src/auth/oauth-metadata.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0CAA0C;AAC1C,8EAA8E;AAuB9E;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,UAAgC,EAAE;IAElC,MAAM,EACJ,QAAQ,GAAG,UAAU,EACrB,MAAM,GAAG,CAAC,UAAU,CAAC,EACrB,kBAAkB,GAAG,IAAI,GAC1B,GAAG,OAAO,CAAC;IAEZ,oCAAoC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEvC,MAAM,QAAQ,GAAkB;QAC9B,MAAM,EAAE,IAAI;QACZ,sBAAsB,EAAE,GAAG,IAAI,GAAG,QAAQ,YAAY;QACtD,cAAc,EAAE,GAAG,IAAI,GAAG,QAAQ,QAAQ;QAC1C,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,qBAAqB,EAAE,CAAC,oBAAoB,EAAE,eAAe,CAAC;QAC9D,gCAAgC,EAAE,CAAC,MAAM,CAAC;QAC1C,qCAAqC,EAAE,CAAC,MAAM,CAAC;QAC/C,gBAAgB,EAAE,MAAM;KACzB,CAAC;IAEF,IAAI,kBAAkB,EAAE,CAAC;QACvB,QAAQ,CAAC,qBAAqB,GAAG,GAAG,IAAI,GAAG,QAAQ,WAAW,CAAC;IACjE,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type OriginValidationResult = {
|
|
2
|
+
valid: true;
|
|
3
|
+
} | {
|
|
4
|
+
valid: false;
|
|
5
|
+
error: string;
|
|
6
|
+
status: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Validate the Origin header for DNS rebinding protection.
|
|
10
|
+
*
|
|
11
|
+
* MCP servers using HTTP transport need to protect against DNS rebinding attacks.
|
|
12
|
+
* This function validates that the request origin is allowed.
|
|
13
|
+
*
|
|
14
|
+
* Allowed origins:
|
|
15
|
+
* - null (CLI clients, curl, etc.)
|
|
16
|
+
* - Same site (matches siteUrl)
|
|
17
|
+
* - Loopback addresses (localhost, 127.0.0.1, [::1])
|
|
18
|
+
* - Non-web protocols (vscode-file://, electron://, tauri://)
|
|
19
|
+
*
|
|
20
|
+
* @param origin - The Origin header value (may be null)
|
|
21
|
+
* @param siteUrl - The allowed site URL (e.g., "https://example.com")
|
|
22
|
+
* @returns Validation result with error details if invalid
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateOrigin(origin: string | null, siteUrl: string): OriginValidationResult;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a hostname is a loopback address.
|
|
27
|
+
*
|
|
28
|
+
* @param hostname - The hostname to check
|
|
29
|
+
* @returns True if loopback
|
|
30
|
+
*/
|
|
31
|
+
export declare function isLoopbackHost(hostname: string): boolean;
|
|
32
|
+
//# sourceMappingURL=origin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../../src/auth/origin.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,sBAAsB,GAC9B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,EAAE,MAAM,GACd,sBAAsB,CAkCxB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMxD"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Origin Validation for DNS Rebinding Protection
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
/**
|
|
5
|
+
* Validate the Origin header for DNS rebinding protection.
|
|
6
|
+
*
|
|
7
|
+
* MCP servers using HTTP transport need to protect against DNS rebinding attacks.
|
|
8
|
+
* This function validates that the request origin is allowed.
|
|
9
|
+
*
|
|
10
|
+
* Allowed origins:
|
|
11
|
+
* - null (CLI clients, curl, etc.)
|
|
12
|
+
* - Same site (matches siteUrl)
|
|
13
|
+
* - Loopback addresses (localhost, 127.0.0.1, [::1])
|
|
14
|
+
* - Non-web protocols (vscode-file://, electron://, tauri://)
|
|
15
|
+
*
|
|
16
|
+
* @param origin - The Origin header value (may be null)
|
|
17
|
+
* @param siteUrl - The allowed site URL (e.g., "https://example.com")
|
|
18
|
+
* @returns Validation result with error details if invalid
|
|
19
|
+
*/
|
|
20
|
+
export function validateOrigin(origin, siteUrl) {
|
|
21
|
+
// Null origin is allowed (CLI clients, curl, etc.)
|
|
22
|
+
if (!origin)
|
|
23
|
+
return { valid: true };
|
|
24
|
+
// Same site is allowed
|
|
25
|
+
try {
|
|
26
|
+
const siteOrigin = new URL(siteUrl).origin;
|
|
27
|
+
if (origin === siteOrigin)
|
|
28
|
+
return { valid: true };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Invalid siteUrl - continue with other checks
|
|
32
|
+
}
|
|
33
|
+
// Non-web protocols are allowed (desktop apps)
|
|
34
|
+
if (!origin.startsWith("http://") && !origin.startsWith("https://")) {
|
|
35
|
+
return { valid: true };
|
|
36
|
+
}
|
|
37
|
+
// Check for loopback addresses
|
|
38
|
+
try {
|
|
39
|
+
const url = new URL(origin);
|
|
40
|
+
const host = url.hostname;
|
|
41
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "[::1]") {
|
|
42
|
+
return { valid: true };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Invalid origin URL
|
|
47
|
+
}
|
|
48
|
+
// All other origins are rejected
|
|
49
|
+
return {
|
|
50
|
+
valid: false,
|
|
51
|
+
error: "Origin not allowed",
|
|
52
|
+
status: 403,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a hostname is a loopback address.
|
|
57
|
+
*
|
|
58
|
+
* @param hostname - The hostname to check
|
|
59
|
+
* @returns True if loopback
|
|
60
|
+
*/
|
|
61
|
+
export function isLoopbackHost(hostname) {
|
|
62
|
+
return (hostname === "localhost" ||
|
|
63
|
+
hostname === "127.0.0.1" ||
|
|
64
|
+
hostname === "[::1]");
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/auth/origin.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAM9E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAqB,EACrB,OAAe;IAEf,mDAAmD;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAEpC,uBAAuB;IACvB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QAC3C,IAAI,MAAM,KAAK,UAAU;YAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;IAED,+CAA+C;IAC/C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,+BAA+B;IAC/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC1B,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;IAED,iCAAiC;IACjC,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,oBAAoB;QAC3B,MAAM,EAAE,GAAG;KACZ,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,OAAO,CACL,QAAQ,KAAK,WAAW;QACxB,QAAQ,KAAK,WAAW;QACxB,QAAQ,KAAK,OAAO,CACrB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify a PKCE S256 code_challenge against a code_verifier.
|
|
3
|
+
*
|
|
4
|
+
* The code_challenge is created by: base64url(sha256(code_verifier))
|
|
5
|
+
* We recompute this from the verifier and compare.
|
|
6
|
+
*
|
|
7
|
+
* @param codeVerifier - The original random string (43-128 chars)
|
|
8
|
+
* @param codeChallenge - The base64url-encoded SHA-256 hash
|
|
9
|
+
* @returns True if the verifier matches the challenge
|
|
10
|
+
*/
|
|
11
|
+
export declare function verifyPkceS256(codeVerifier: string, codeChallenge: string): Promise<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* Generate a PKCE code_verifier (for testing purposes).
|
|
14
|
+
*
|
|
15
|
+
* In production, this is typically done client-side.
|
|
16
|
+
*
|
|
17
|
+
* @param length - Length of the verifier (43-128 chars, default 64)
|
|
18
|
+
* @returns A random code_verifier string
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateCodeVerifier(length?: number): string;
|
|
21
|
+
/**
|
|
22
|
+
* Generate a PKCE code_challenge from a code_verifier (for testing purposes).
|
|
23
|
+
*
|
|
24
|
+
* @param codeVerifier - The code_verifier to hash
|
|
25
|
+
* @returns The base64url-encoded SHA-256 hash
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateCodeChallenge(codeVerifier: string): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Check if a redirect URI is a loopback address (allowed for native apps).
|
|
30
|
+
*
|
|
31
|
+
* Per RFC 8252, native apps can use http:// with loopback addresses:
|
|
32
|
+
* - localhost
|
|
33
|
+
* - 127.0.0.1
|
|
34
|
+
* - [::1]
|
|
35
|
+
*
|
|
36
|
+
* @param uri - The redirect_uri to check
|
|
37
|
+
* @returns True if it's a valid loopback URI
|
|
38
|
+
*/
|
|
39
|
+
export declare function isLoopbackRedirectUri(uri: string): boolean;
|
|
40
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/auth/pkce.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,SAAK,GAAG,MAAM,CAQxD;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAS1D"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// OAuth 2.1 PKCE (Proof Key for Code Exchange) Verification
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
/**
|
|
5
|
+
* Verify a PKCE S256 code_challenge against a code_verifier.
|
|
6
|
+
*
|
|
7
|
+
* The code_challenge is created by: base64url(sha256(code_verifier))
|
|
8
|
+
* We recompute this from the verifier and compare.
|
|
9
|
+
*
|
|
10
|
+
* @param codeVerifier - The original random string (43-128 chars)
|
|
11
|
+
* @param codeChallenge - The base64url-encoded SHA-256 hash
|
|
12
|
+
* @returns True if the verifier matches the challenge
|
|
13
|
+
*/
|
|
14
|
+
export async function verifyPkceS256(codeVerifier, codeChallenge) {
|
|
15
|
+
if (!codeVerifier || !codeChallenge)
|
|
16
|
+
return false;
|
|
17
|
+
const encoded = new TextEncoder().encode(codeVerifier);
|
|
18
|
+
const buffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
19
|
+
const base64url = btoa(String.fromCharCode(...new Uint8Array(buffer)))
|
|
20
|
+
.replace(/\+/g, "-")
|
|
21
|
+
.replace(/\//g, "_")
|
|
22
|
+
.replace(/=+$/, "");
|
|
23
|
+
return base64url === codeChallenge;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate a PKCE code_verifier (for testing purposes).
|
|
27
|
+
*
|
|
28
|
+
* In production, this is typically done client-side.
|
|
29
|
+
*
|
|
30
|
+
* @param length - Length of the verifier (43-128 chars, default 64)
|
|
31
|
+
* @returns A random code_verifier string
|
|
32
|
+
*/
|
|
33
|
+
export function generateCodeVerifier(length = 64) {
|
|
34
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
35
|
+
const array = new Uint8Array(length);
|
|
36
|
+
crypto.getRandomValues(array);
|
|
37
|
+
return Array.from(array)
|
|
38
|
+
.map((b) => chars[b % chars.length])
|
|
39
|
+
.join("");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate a PKCE code_challenge from a code_verifier (for testing purposes).
|
|
43
|
+
*
|
|
44
|
+
* @param codeVerifier - The code_verifier to hash
|
|
45
|
+
* @returns The base64url-encoded SHA-256 hash
|
|
46
|
+
*/
|
|
47
|
+
export async function generateCodeChallenge(codeVerifier) {
|
|
48
|
+
const encoded = new TextEncoder().encode(codeVerifier);
|
|
49
|
+
const buffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
50
|
+
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
|
|
51
|
+
.replace(/\+/g, "-")
|
|
52
|
+
.replace(/\//g, "_")
|
|
53
|
+
.replace(/=+$/, "");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a redirect URI is a loopback address (allowed for native apps).
|
|
57
|
+
*
|
|
58
|
+
* Per RFC 8252, native apps can use http:// with loopback addresses:
|
|
59
|
+
* - localhost
|
|
60
|
+
* - 127.0.0.1
|
|
61
|
+
* - [::1]
|
|
62
|
+
*
|
|
63
|
+
* @param uri - The redirect_uri to check
|
|
64
|
+
* @returns True if it's a valid loopback URI
|
|
65
|
+
*/
|
|
66
|
+
export function isLoopbackRedirectUri(uri) {
|
|
67
|
+
try {
|
|
68
|
+
const url = new URL(uri);
|
|
69
|
+
if (url.protocol !== "http:")
|
|
70
|
+
return false;
|
|
71
|
+
const host = url.hostname;
|
|
72
|
+
return host === "localhost" || host === "127.0.0.1" || host === "[::1]";
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/auth/pkce.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,4DAA4D;AAC5D,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,YAAoB,EACpB,aAAqB;IAErB,IAAI,CAAC,YAAY,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAC;IAElD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;SACnE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtB,OAAO,SAAS,KAAK,aAAa,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAM,GAAG,EAAE;IAC9C,MAAM,KAAK,GACT,oEAAoE,CAAC;IACvE,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;SACnC,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,YAAoB;IAEpB,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;SACxD,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3C,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC1B,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,OAAO,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a secure random token.
|
|
3
|
+
*
|
|
4
|
+
* @param length - Number of random bytes (default 32, produces 64 hex chars)
|
|
5
|
+
* @returns Hex-encoded random token
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateToken(length?: number): string;
|
|
8
|
+
/**
|
|
9
|
+
* Hash a token using SHA-256.
|
|
10
|
+
*
|
|
11
|
+
* Tokens should be stored as hashes, never in plaintext.
|
|
12
|
+
*
|
|
13
|
+
* @param token - The plaintext token
|
|
14
|
+
* @returns SHA-256 hash as hex string
|
|
15
|
+
*/
|
|
16
|
+
export declare function hashToken(token: string): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Generate a token preview (first 8 characters).
|
|
19
|
+
*
|
|
20
|
+
* Used for logging and display without exposing the full token.
|
|
21
|
+
*
|
|
22
|
+
* @param token - The plaintext token
|
|
23
|
+
* @returns First 8 characters
|
|
24
|
+
*/
|
|
25
|
+
export declare function tokenPreview(token: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Extract bearer token from Authorization header.
|
|
28
|
+
*
|
|
29
|
+
* @param authHeader - The Authorization header value
|
|
30
|
+
* @returns The token, or null if invalid
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractBearerToken(authHeader: string | null): string | null;
|
|
33
|
+
export interface TokenValidationSuccess {
|
|
34
|
+
valid: true;
|
|
35
|
+
token: {
|
|
36
|
+
id: string;
|
|
37
|
+
user_id: string;
|
|
38
|
+
client_id: string;
|
|
39
|
+
scope: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface TokenValidationError {
|
|
43
|
+
valid: false;
|
|
44
|
+
error: string;
|
|
45
|
+
status: number;
|
|
46
|
+
}
|
|
47
|
+
export type TokenValidationResult = TokenValidationSuccess | TokenValidationError;
|
|
48
|
+
/**
|
|
49
|
+
* Token store interface for validation.
|
|
50
|
+
*
|
|
51
|
+
* Implement this interface to connect to your token storage.
|
|
52
|
+
*/
|
|
53
|
+
export interface TokenStore {
|
|
54
|
+
/**
|
|
55
|
+
* Find a token by its hash.
|
|
56
|
+
* @returns Token record or null if not found
|
|
57
|
+
*/
|
|
58
|
+
findByHash(hash: string): Promise<{
|
|
59
|
+
id: string;
|
|
60
|
+
user_id: string;
|
|
61
|
+
client_id: string;
|
|
62
|
+
scope: string;
|
|
63
|
+
revoked: boolean;
|
|
64
|
+
expires_at: string | null;
|
|
65
|
+
} | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Update last_used_at timestamp.
|
|
68
|
+
*/
|
|
69
|
+
updateLastUsed(id: string): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate an MCP bearer token.
|
|
73
|
+
*
|
|
74
|
+
* @param store - Token storage implementation
|
|
75
|
+
* @param authHeader - The Authorization header value
|
|
76
|
+
* @returns Validation result
|
|
77
|
+
*/
|
|
78
|
+
export declare function validateMcpToken(store: TokenStore, authHeader: string | null): Promise<TokenValidationResult>;
|
|
79
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/auth/token.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,SAAK,GAAG,MAAM,CAMjD;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9D;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAI3E;AAMD,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,IAAI,CAAC;IACZ,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG,oBAAoB,CAAC;AAElF;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAChC,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GAAG,IAAI,CAAC,CAAC;IAEV;;OAEG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAmDhC"}
|