@bayonai/mcp 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-07-06
4
+
5
+ - Added OAuth protected-resource metadata helpers.
6
+ - Added OAuth authorization-server metadata helpers.
7
+ - Added MCP bearer `WWW-Authenticate` challenge helper.
8
+ - Added PKCE S256 challenge creation and verification.
9
+ - Added URL-safe token generation, SHA-256 secret hashing, and constant-time
10
+ comparison helpers.
11
+ - Added dual CJS/ESM package output and packed-package smoke verification.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # @bayonai/mcp
2
+
3
+ Shared MCP OAuth and protocol helpers for Bayon apps.
4
+
5
+ This package contains app-agnostic helpers for remote MCP servers:
6
+
7
+ - OAuth protected-resource and authorization-server metadata.
8
+ - `WWW-Authenticate` bearer challenges that point MCP clients to metadata.
9
+ - PKCE S256 challenge creation and verification.
10
+ - Secret hashing, random token generation, and constant-time comparisons.
11
+
12
+ Application code remains responsible for its own MCP tools/resources/prompts,
13
+ business authorization, persistence, audit logging, and user-consent UI.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ pnpm add @bayonai/mcp
19
+ ```
20
+
21
+ ## Metadata
22
+
23
+ ```ts
24
+ import {
25
+ createMcpWwwAuthenticateHeader,
26
+ getOAuthAuthorizationServerMetadata,
27
+ getOAuthProtectedResourceMetadata,
28
+ } from "@bayonai/mcp";
29
+
30
+ const baseUrl = "https://bounded.example/api";
31
+ const scopes = ["bounded.read", "bounded.write"];
32
+
33
+ export const protectedResource = getOAuthProtectedResourceMetadata({
34
+ documentationUrl: "https://bounded.example/docs/mcp",
35
+ scopes,
36
+ serviceUrl: baseUrl,
37
+ });
38
+
39
+ export const authorizationServer = getOAuthAuthorizationServerMetadata({
40
+ scopes,
41
+ serviceUrl: baseUrl,
42
+ });
43
+
44
+ export const wwwAuthenticate = createMcpWwwAuthenticateHeader({
45
+ metadataUrl: `${baseUrl}/.well-known/oauth-protected-resource`,
46
+ realm: "Bounded MCP",
47
+ scopes,
48
+ });
49
+ ```
50
+
51
+ ## PKCE
52
+
53
+ ```ts
54
+ import { buildPkceChallenge, verifyPkceChallenge } from "@bayonai/mcp";
55
+
56
+ const challenge = buildPkceChallenge(codeVerifier);
57
+
58
+ if (!verifyPkceChallenge({ challenge, method: "S256", verifier })) {
59
+ throw new Error("Invalid PKCE verifier.");
60
+ }
61
+ ```
62
+
63
+ ## Token Secrets
64
+
65
+ ```ts
66
+ import { createMcpRandomToken, hashMcpSecret } from "@bayonai/mcp";
67
+
68
+ const accessToken = `app_mcp_access_${createMcpRandomToken(40)}`;
69
+ const accessTokenHash = hashMcpSecret(accessToken);
70
+ ```
71
+
72
+ Store token hashes, not bearer tokens. The app owns expiry, revocation, refresh
73
+ rotation, and user/session lookup.
74
+
75
+ ## Package Boundaries
76
+
77
+ Keep these app-specific concerns outside `@bayonai/mcp`:
78
+
79
+ - Tool/resource/prompt registries.
80
+ - Firestore collection names and indexes.
81
+ - Firebase Auth, service-token, or admin consent flows.
82
+ - Product-specific scopes such as `thunderlist.read` or `bounded.write`.
83
+ - Audit logging and mutation authorization.
84
+
85
+ ## Release Checks
86
+
87
+ ```sh
88
+ corepack pnpm --dir packages/bayonai-mcp run lint
89
+ corepack pnpm --dir packages/bayonai-mcp run build
90
+ corepack pnpm exec vitest run packages/bayonai-mcp/src/metadata.test.ts packages/bayonai-mcp/src/pkce.test.ts packages/bayonai-mcp/src/secrets.test.ts
91
+ corepack pnpm --dir packages/bayonai-mcp run smoke:pack
92
+ ```
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./metadata.js"), exports);
18
+ __exportStar(require("./pkce.js"), exports);
19
+ __exportStar(require("./secrets.js"), exports);
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,4CAA0B;AAC1B,+CAA6B"}
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOAuthProtectedResourceMetadata = getOAuthProtectedResourceMetadata;
4
+ exports.getOAuthAuthorizationServerMetadata = getOAuthAuthorizationServerMetadata;
5
+ exports.createMcpWwwAuthenticateHeader = createMcpWwwAuthenticateHeader;
6
+ exports.normalizeMcpScopes = normalizeMcpScopes;
7
+ function getOAuthProtectedResourceMetadata(options) {
8
+ const serviceUrl = normalizeBaseUrl(assertAbsoluteUrl(options.serviceUrl));
9
+ const authorizationServerUrl = normalizeBaseUrl(assertAbsoluteUrl(options.authorizationServerUrl ?? serviceUrl));
10
+ const resourcePath = assertAbsolutePath(options.resourcePath ?? "/mcp");
11
+ const scopes = normalizeMcpScopes(options.scopes);
12
+ return {
13
+ authorization_servers: [authorizationServerUrl],
14
+ bearer_methods_supported: ["header"],
15
+ ...(options.documentationUrl
16
+ ? { resource_documentation: options.documentationUrl }
17
+ : {}),
18
+ resource: `${serviceUrl}${resourcePath}`,
19
+ scopes_supported: scopes,
20
+ };
21
+ }
22
+ function getOAuthAuthorizationServerMetadata(options) {
23
+ const authorizationServerUrl = normalizeBaseUrl(assertAbsoluteUrl(options.authorizationServerUrl ?? options.serviceUrl));
24
+ const scopes = normalizeMcpScopes(options.scopes);
25
+ return {
26
+ authorization_endpoint: `${authorizationServerUrl}/oauth/authorize`,
27
+ code_challenge_methods_supported: ["S256"],
28
+ grant_types_supported: ["authorization_code", "refresh_token"],
29
+ issuer: authorizationServerUrl,
30
+ registration_endpoint: `${authorizationServerUrl}/oauth/register`,
31
+ response_types_supported: ["code"],
32
+ scopes_supported: scopes,
33
+ token_endpoint: `${authorizationServerUrl}/oauth/token`,
34
+ token_endpoint_auth_methods_supported: ["none"],
35
+ };
36
+ }
37
+ function createMcpWwwAuthenticateHeader({ metadataUrl, realm, scopes, }) {
38
+ const normalizedScopes = normalizeMcpScopes(scopes);
39
+ return [
40
+ `Bearer realm="${escapeHeaderValue(realm)}"`,
41
+ `resource_metadata="${escapeHeaderValue(assertAbsoluteUrl(metadataUrl))}"`,
42
+ `scope="${escapeHeaderValue(normalizedScopes.join(" "))}"`,
43
+ ].join(", ");
44
+ }
45
+ function normalizeMcpScopes(scopes) {
46
+ const normalizedScopes = scopes.map((scope) => scope.trim()).filter(Boolean);
47
+ if (!normalizedScopes.length) {
48
+ throw new Error("MCP OAuth scopes must include at least one scope.");
49
+ }
50
+ return [...new Set(normalizedScopes)];
51
+ }
52
+ function assertAbsoluteUrl(value) {
53
+ const url = new URL(value);
54
+ if (url.protocol !== "https:" && url.hostname !== "localhost") {
55
+ throw new Error("MCP OAuth URLs must use https, except localhost.");
56
+ }
57
+ return url.toString();
58
+ }
59
+ function normalizeBaseUrl(baseUrl) {
60
+ return baseUrl.replace(/\/+$/, "");
61
+ }
62
+ function assertAbsolutePath(path) {
63
+ if (!path.startsWith("/")) {
64
+ throw new Error("MCP OAuth resourcePath must start with /.");
65
+ }
66
+ return path;
67
+ }
68
+ function escapeHeaderValue(value) {
69
+ if (/[\u0000-\u001f\u007f]/u.test(value)) {
70
+ throw new Error("MCP OAuth header values cannot contain control characters.");
71
+ }
72
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
73
+ }
74
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPkceChallenge = buildPkceChallenge;
4
+ exports.verifyPkceChallenge = verifyPkceChallenge;
5
+ const crypto_1 = require("crypto");
6
+ function buildPkceChallenge(verifier) {
7
+ return (0, crypto_1.createHash)("sha256").update(verifier).digest("base64url");
8
+ }
9
+ function verifyPkceChallenge({ challenge, method, verifier, }) {
10
+ if (method !== "S256") {
11
+ return false;
12
+ }
13
+ const expectedChallenge = buildPkceChallenge(verifier);
14
+ const expected = Buffer.from(expectedChallenge);
15
+ const actual = Buffer.from(challenge);
16
+ return expected.length === actual.length && (0, crypto_1.timingSafeEqual)(expected, actual);
17
+ }
18
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/pkce.ts"],"names":[],"mappings":";;AAEA,gDAEC;AAED,kDAkBC;AAxBD,mCAAqD;AAErD,SAAgB,kBAAkB,CAAC,QAAgB;IACjD,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AACnE,CAAC;AAED,SAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,MAAM,EACN,QAAQ,GAKT;IACC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtC,OAAO,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,IAAA,wBAAe,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMcpRandomToken = createMcpRandomToken;
4
+ exports.hashMcpSecret = hashMcpSecret;
5
+ exports.constantTimeEqual = constantTimeEqual;
6
+ const crypto_1 = require("crypto");
7
+ function createMcpRandomToken(bytes) {
8
+ return (0, crypto_1.randomBytes)(bytes).toString("base64url");
9
+ }
10
+ function hashMcpSecret(secret) {
11
+ return (0, crypto_1.createHash)("sha256").update(secret).digest("base64url");
12
+ }
13
+ function constantTimeEqual(left, right) {
14
+ const leftBuffer = Buffer.from(left);
15
+ const rightBuffer = Buffer.from(right);
16
+ return (leftBuffer.length === rightBuffer.length &&
17
+ (0, crypto_1.timingSafeEqual)(leftBuffer, rightBuffer));
18
+ }
19
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/secrets.ts"],"names":[],"mappings":";;AAEA,oDAEC;AAED,sCAEC;AAED,8CAQC;AAlBD,mCAAkE;AAElE,SAAgB,oBAAoB,CAAC,KAAa;IAChD,OAAO,IAAA,oBAAW,EAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAClD,CAAC;AAED,SAAgB,aAAa,CAAC,MAAc;IAC1C,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AACjE,CAAC;AAED,SAAgB,iBAAiB,CAAC,IAAY,EAAE,KAAa;IAC3D,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAEvC,OAAO,CACL,UAAU,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM;QACxC,IAAA,wBAAe,EAAC,UAAU,EAAE,WAAW,CAAC,CACzC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./metadata.js";
2
+ export * from "./pkce.js";
3
+ export * from "./secrets.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,68 @@
1
+ export function getOAuthProtectedResourceMetadata(options) {
2
+ const serviceUrl = normalizeBaseUrl(assertAbsoluteUrl(options.serviceUrl));
3
+ const authorizationServerUrl = normalizeBaseUrl(assertAbsoluteUrl(options.authorizationServerUrl ?? serviceUrl));
4
+ const resourcePath = assertAbsolutePath(options.resourcePath ?? "/mcp");
5
+ const scopes = normalizeMcpScopes(options.scopes);
6
+ return {
7
+ authorization_servers: [authorizationServerUrl],
8
+ bearer_methods_supported: ["header"],
9
+ ...(options.documentationUrl
10
+ ? { resource_documentation: options.documentationUrl }
11
+ : {}),
12
+ resource: `${serviceUrl}${resourcePath}`,
13
+ scopes_supported: scopes,
14
+ };
15
+ }
16
+ export function getOAuthAuthorizationServerMetadata(options) {
17
+ const authorizationServerUrl = normalizeBaseUrl(assertAbsoluteUrl(options.authorizationServerUrl ?? options.serviceUrl));
18
+ const scopes = normalizeMcpScopes(options.scopes);
19
+ return {
20
+ authorization_endpoint: `${authorizationServerUrl}/oauth/authorize`,
21
+ code_challenge_methods_supported: ["S256"],
22
+ grant_types_supported: ["authorization_code", "refresh_token"],
23
+ issuer: authorizationServerUrl,
24
+ registration_endpoint: `${authorizationServerUrl}/oauth/register`,
25
+ response_types_supported: ["code"],
26
+ scopes_supported: scopes,
27
+ token_endpoint: `${authorizationServerUrl}/oauth/token`,
28
+ token_endpoint_auth_methods_supported: ["none"],
29
+ };
30
+ }
31
+ export function createMcpWwwAuthenticateHeader({ metadataUrl, realm, scopes, }) {
32
+ const normalizedScopes = normalizeMcpScopes(scopes);
33
+ return [
34
+ `Bearer realm="${escapeHeaderValue(realm)}"`,
35
+ `resource_metadata="${escapeHeaderValue(assertAbsoluteUrl(metadataUrl))}"`,
36
+ `scope="${escapeHeaderValue(normalizedScopes.join(" "))}"`,
37
+ ].join(", ");
38
+ }
39
+ export function normalizeMcpScopes(scopes) {
40
+ const normalizedScopes = scopes.map((scope) => scope.trim()).filter(Boolean);
41
+ if (!normalizedScopes.length) {
42
+ throw new Error("MCP OAuth scopes must include at least one scope.");
43
+ }
44
+ return [...new Set(normalizedScopes)];
45
+ }
46
+ function assertAbsoluteUrl(value) {
47
+ const url = new URL(value);
48
+ if (url.protocol !== "https:" && url.hostname !== "localhost") {
49
+ throw new Error("MCP OAuth URLs must use https, except localhost.");
50
+ }
51
+ return url.toString();
52
+ }
53
+ function normalizeBaseUrl(baseUrl) {
54
+ return baseUrl.replace(/\/+$/, "");
55
+ }
56
+ function assertAbsolutePath(path) {
57
+ if (!path.startsWith("/")) {
58
+ throw new Error("MCP OAuth resourcePath must start with /.");
59
+ }
60
+ return path;
61
+ }
62
+ function escapeHeaderValue(value) {
63
+ if (/[\u0000-\u001f\u007f]/u.test(value)) {
64
+ throw new Error("MCP OAuth header values cannot contain control characters.");
65
+ }
66
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
67
+ }
68
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,14 @@
1
+ import { createHash, timingSafeEqual } from "crypto";
2
+ export function buildPkceChallenge(verifier) {
3
+ return createHash("sha256").update(verifier).digest("base64url");
4
+ }
5
+ export function verifyPkceChallenge({ challenge, method, verifier, }) {
6
+ if (method !== "S256") {
7
+ return false;
8
+ }
9
+ const expectedChallenge = buildPkceChallenge(verifier);
10
+ const expected = Buffer.from(expectedChallenge);
11
+ const actual = Buffer.from(challenge);
12
+ return expected.length === actual.length && timingSafeEqual(expected, actual);
13
+ }
14
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAErD,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,EAClC,SAAS,EACT,MAAM,EACN,QAAQ,GAKT;IACC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtC,OAAO,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from "crypto";
2
+ export function createMcpRandomToken(bytes) {
3
+ return randomBytes(bytes).toString("base64url");
4
+ }
5
+ export function hashMcpSecret(secret) {
6
+ return createHash("sha256").update(secret).digest("base64url");
7
+ }
8
+ export function constantTimeEqual(left, right) {
9
+ const leftBuffer = Buffer.from(left);
10
+ const rightBuffer = Buffer.from(right);
11
+ return (leftBuffer.length === rightBuffer.length &&
12
+ timingSafeEqual(leftBuffer, rightBuffer));
13
+ }
14
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/secrets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAElE,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAa;IAC3D,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAEvC,OAAO,CACL,UAAU,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM;QACxC,eAAe,CAAC,UAAU,EAAE,WAAW,CAAC,CACzC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from "./metadata.js";
2
+ export * from "./pkce.js";
3
+ export * from "./secrets.js";
@@ -0,0 +1,31 @@
1
+ export type McpOAuthMetadataOptions = {
2
+ authorizationServerUrl?: string;
3
+ documentationUrl?: string;
4
+ resourcePath?: string;
5
+ scopes: readonly string[];
6
+ serviceUrl: string;
7
+ };
8
+ export declare function getOAuthProtectedResourceMetadata(options: McpOAuthMetadataOptions): {
9
+ resource: string;
10
+ scopes_supported: string[];
11
+ resource_documentation?: string | undefined;
12
+ authorization_servers: string[];
13
+ bearer_methods_supported: string[];
14
+ };
15
+ export declare function getOAuthAuthorizationServerMetadata(options: McpOAuthMetadataOptions): {
16
+ authorization_endpoint: string;
17
+ code_challenge_methods_supported: string[];
18
+ grant_types_supported: string[];
19
+ issuer: string;
20
+ registration_endpoint: string;
21
+ response_types_supported: string[];
22
+ scopes_supported: string[];
23
+ token_endpoint: string;
24
+ token_endpoint_auth_methods_supported: string[];
25
+ };
26
+ export declare function createMcpWwwAuthenticateHeader({ metadataUrl, realm, scopes, }: {
27
+ metadataUrl: string;
28
+ realm: string;
29
+ scopes: readonly string[];
30
+ }): string;
31
+ export declare function normalizeMcpScopes(scopes: readonly string[]): string[];
package/dist/pkce.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export declare function buildPkceChallenge(verifier: string): string;
2
+ export declare function verifyPkceChallenge({ challenge, method, verifier, }: {
3
+ challenge: string;
4
+ method: string;
5
+ verifier: string;
6
+ }): boolean;
@@ -0,0 +1,3 @@
1
+ export declare function createMcpRandomToken(bytes: number): string;
2
+ export declare function hashMcpSecret(secret: string): string;
3
+ export declare function constantTimeEqual(left: string, right: string): boolean;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@bayonai/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Shared MCP OAuth and protocol helpers for Bayon apps.",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "oauth",
9
+ "pkce",
10
+ "bayon"
11
+ ],
12
+ "homepage": "https://github.com/toledoroy/Thunderlist#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/toledoroy/Thunderlist/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/toledoroy/Thunderlist.git",
19
+ "directory": "packages/bayonai-mcp"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/esm/index.js",
25
+ "require": "./dist/cjs/index.js",
26
+ "default": "./dist/esm/index.js"
27
+ }
28
+ },
29
+ "main": "dist/cjs/index.js",
30
+ "module": "dist/esm/index.js",
31
+ "types": "dist/index.d.ts",
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "CHANGELOG.md"
36
+ ],
37
+ "sideEffects": false,
38
+ "scripts": {
39
+ "build": "node scripts/clean-dist.mjs && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && node scripts/prepare-dist.mjs",
40
+ "lint": "tsc --noEmit -p tsconfig.cjs.json && tsc --noEmit -p tsconfig.esm.json",
41
+ "prepack": "pnpm run build",
42
+ "smoke:pack": "node scripts/smoke-package.mjs"
43
+ },
44
+ "engines": {
45
+ "node": ">=20"
46
+ },
47
+ "license": "UNLICENSED",
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20",
53
+ "typescript": "^5"
54
+ }
55
+ }