@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.
Files changed (66) hide show
  1. package/README.md +194 -0
  2. package/dist/auth/index.d.ts +5 -0
  3. package/dist/auth/index.d.ts.map +1 -0
  4. package/dist/auth/index.js +6 -0
  5. package/dist/auth/index.js.map +1 -0
  6. package/dist/auth/oauth-metadata.d.ts +31 -0
  7. package/dist/auth/oauth-metadata.d.ts.map +1 -0
  8. package/dist/auth/oauth-metadata.js +33 -0
  9. package/dist/auth/oauth-metadata.js.map +1 -0
  10. package/dist/auth/origin.d.ts +32 -0
  11. package/dist/auth/origin.d.ts.map +1 -0
  12. package/dist/auth/origin.js +66 -0
  13. package/dist/auth/origin.js.map +1 -0
  14. package/dist/auth/pkce.d.ts +40 -0
  15. package/dist/auth/pkce.d.ts.map +1 -0
  16. package/dist/auth/pkce.js +78 -0
  17. package/dist/auth/pkce.js.map +1 -0
  18. package/dist/auth/token.d.ts +79 -0
  19. package/dist/auth/token.d.ts.map +1 -0
  20. package/dist/auth/token.js +108 -0
  21. package/dist/auth/token.js.map +1 -0
  22. package/dist/framework/handlers.d.ts +14 -0
  23. package/dist/framework/handlers.d.ts.map +1 -0
  24. package/dist/framework/handlers.js +110 -0
  25. package/dist/framework/handlers.js.map +1 -0
  26. package/dist/framework/index.d.ts +7 -0
  27. package/dist/framework/index.d.ts.map +1 -0
  28. package/dist/framework/index.js +7 -0
  29. package/dist/framework/index.js.map +1 -0
  30. package/dist/framework/projection.d.ts +10 -0
  31. package/dist/framework/projection.d.ts.map +1 -0
  32. package/dist/framework/projection.js +21 -0
  33. package/dist/framework/projection.js.map +1 -0
  34. package/dist/framework/register.d.ts +16 -0
  35. package/dist/framework/register.d.ts.map +1 -0
  36. package/dist/framework/register.js +70 -0
  37. package/dist/framework/register.js.map +1 -0
  38. package/dist/framework/resolve.d.ts +39 -0
  39. package/dist/framework/resolve.d.ts.map +1 -0
  40. package/dist/framework/resolve.js +46 -0
  41. package/dist/framework/resolve.js.map +1 -0
  42. package/dist/framework/response.d.ts +6 -0
  43. package/dist/framework/response.d.ts.map +1 -0
  44. package/dist/framework/response.js +17 -0
  45. package/dist/framework/response.js.map +1 -0
  46. package/dist/framework/types.d.ts +85 -0
  47. package/dist/framework/types.d.ts.map +1 -0
  48. package/dist/framework/types.js +5 -0
  49. package/dist/framework/types.js.map +1 -0
  50. package/dist/index.d.ts +4 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +6 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/server/create-server.d.ts +24 -0
  55. package/dist/server/create-server.d.ts.map +1 -0
  56. package/dist/server/create-server.js +24 -0
  57. package/dist/server/create-server.js.map +1 -0
  58. package/dist/testing/index.d.ts +2 -0
  59. package/dist/testing/index.d.ts.map +1 -0
  60. package/dist/testing/index.js +3 -0
  61. package/dist/testing/index.js.map +1 -0
  62. package/dist/testing/utils.d.ts +55 -0
  63. package/dist/testing/utils.d.ts.map +1 -0
  64. package/dist/testing/utils.js +66 -0
  65. package/dist/testing/utils.js.map +1 -0
  66. 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"}