@mcp-z/client 1.2.0 → 2.0.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/dist/cjs/auth/capability-discovery.js +5 -1
- package/dist/cjs/auth/capability-discovery.js.map +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.d.cts +9 -2
- package/dist/cjs/auth/interactive-oauth-flow.d.ts +9 -2
- package/dist/cjs/auth/interactive-oauth-flow.js +29 -11
- package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/cjs/auth/oauth-callback-listener.js +4 -0
- package/dist/cjs/auth/oauth-callback-listener.js.map +1 -1
- package/dist/cjs/auth/types.d.cts +18 -4
- package/dist/cjs/auth/types.d.ts +18 -4
- package/dist/cjs/auth/types.js.map +1 -1
- package/dist/cjs/dcr/dcr-authenticator.d.cts +6 -1
- package/dist/cjs/dcr/dcr-authenticator.d.ts +6 -1
- package/dist/cjs/dcr/dcr-authenticator.js +423 -40
- package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
- package/dist/cjs/dcr/dynamic-client-registrar.d.cts +2 -2
- package/dist/cjs/dcr/dynamic-client-registrar.d.ts +2 -2
- package/dist/cjs/dcr/dynamic-client-registrar.js +10 -11
- package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/cjs/lib/url-utils.js +2 -2
- package/dist/cjs/lib/url-utils.js.map +1 -1
- package/dist/esm/auth/capability-discovery.js +5 -1
- package/dist/esm/auth/capability-discovery.js.map +1 -1
- package/dist/esm/auth/interactive-oauth-flow.d.ts +9 -2
- package/dist/esm/auth/interactive-oauth-flow.js +28 -10
- package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/esm/auth/oauth-callback-listener.js +4 -0
- package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
- package/dist/esm/auth/types.d.ts +18 -4
- package/dist/esm/auth/types.js.map +1 -1
- package/dist/esm/dcr/dcr-authenticator.d.ts +6 -1
- package/dist/esm/dcr/dcr-authenticator.js +79 -35
- package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
- package/dist/esm/dcr/dynamic-client-registrar.d.ts +2 -2
- package/dist/esm/dcr/dynamic-client-registrar.js +7 -6
- package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/esm/lib/url-utils.js +2 -2
- package/dist/esm/lib/url-utils.js.map +1 -1
- package/package.json +2 -2
package/dist/esm/auth/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface CallbackResult {
|
|
|
9
9
|
code: string;
|
|
10
10
|
/** State parameter for CSRF protection */
|
|
11
11
|
state?: string;
|
|
12
|
+
/** Issuer identifier of the authorization server that minted the response (RFC 9207) */
|
|
13
|
+
iss?: string;
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* PKCE (Proof Key for Code Exchange) parameters (RFC 7636)
|
|
@@ -38,6 +40,8 @@ export interface TokenSet {
|
|
|
38
40
|
clientId?: string;
|
|
39
41
|
/** Client secret used for DCR registration (stored for future use) */
|
|
40
42
|
clientSecret?: string;
|
|
43
|
+
/** Issuer identifier of the authorization server these credentials belong to (SEP-2352) */
|
|
44
|
+
issuer?: string;
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
43
47
|
* OAuth 2.0 Protected Resource Metadata (RFC 9728)
|
|
@@ -76,6 +80,8 @@ export interface AuthorizationServerMetadata {
|
|
|
76
80
|
grant_types_supported?: string[];
|
|
77
81
|
/** Token endpoint authentication methods supported */
|
|
78
82
|
token_endpoint_auth_methods_supported?: string[];
|
|
83
|
+
/** Whether the authorization response carries an `iss` parameter (RFC 9207) */
|
|
84
|
+
authorization_response_iss_parameter_supported?: boolean;
|
|
79
85
|
}
|
|
80
86
|
/**
|
|
81
87
|
* OAuth server capabilities discovered from .well-known endpoint
|
|
@@ -83,6 +89,10 @@ export interface AuthorizationServerMetadata {
|
|
|
83
89
|
export interface AuthCapabilities {
|
|
84
90
|
/** Whether the server supports Dynamic Client Registration (RFC 7591) */
|
|
85
91
|
supportsDcr: boolean;
|
|
92
|
+
/** Issuer identifier from the authorization server metadata (RFC 8414) */
|
|
93
|
+
issuer?: string;
|
|
94
|
+
/** Whether the authorization response carries an `iss` parameter (RFC 9207) */
|
|
95
|
+
authorizationResponseIssSupported?: boolean;
|
|
86
96
|
/** DCR client registration endpoint */
|
|
87
97
|
registrationEndpoint?: string;
|
|
88
98
|
/** OAuth authorization endpoint */
|
|
@@ -109,10 +119,10 @@ export interface ClientCredentials {
|
|
|
109
119
|
* Options for DCR client registration
|
|
110
120
|
*/
|
|
111
121
|
export interface DcrRegistrationOptions {
|
|
122
|
+
/** Redirect URI for OAuth callback, from the loopback listener the caller has already bound (RFC 8252) */
|
|
123
|
+
redirectUri: string;
|
|
112
124
|
/** Client name to register */
|
|
113
125
|
clientName?: string;
|
|
114
|
-
/** Redirect URI for OAuth callback */
|
|
115
|
-
redirectUri?: string;
|
|
116
126
|
/**
|
|
117
127
|
* Loopback trust grant for the registration_endpoint fetch (SSRF
|
|
118
128
|
* mitigation - see `src/auth/discovery-fetch.ts`). Compute this from the
|
|
@@ -128,12 +138,16 @@ export interface DcrRegistrationOptions {
|
|
|
128
138
|
export interface OAuthFlowOptions {
|
|
129
139
|
/** Port for OAuth callback listener (required - use get-port to find available port) */
|
|
130
140
|
port: number;
|
|
141
|
+
/** Issuer identifier discovered before the flow starts; the `iss` in the authorization response must match it (RFC 9207) */
|
|
142
|
+
issuer: string;
|
|
143
|
+
/** Canonical resource server URI, sent as `resource` on the authorization, token, and refresh requests (RFC 8707) */
|
|
144
|
+
resource: string;
|
|
131
145
|
/** Redirect URI for OAuth callback (optional - will be built from port if not provided) */
|
|
132
146
|
redirectUri?: string;
|
|
133
147
|
/** OAuth scopes to request */
|
|
134
148
|
scopes?: string[];
|
|
135
|
-
/**
|
|
136
|
-
|
|
149
|
+
/** Whether the authorization server advertises `authorization_response_iss_parameter_supported` (RFC 9207) */
|
|
150
|
+
authorizationResponseIssSupported?: boolean;
|
|
137
151
|
/** Enable PKCE (RFC 7636) - recommended for all clients, required for public clients */
|
|
138
152
|
pkce?: boolean;
|
|
139
153
|
/** Headless mode (don't open browser) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/types.ts"],"sourcesContent":["/**\n * Shared types for OAuth and DCR authentication\n */\n\n/**\n * OAuth callback result from authorization server\n */\nexport interface CallbackResult {\n /** Authorization code from OAuth server */\n code: string;\n /** State parameter for CSRF protection */\n state?: string;\n}\n\n/**\n * PKCE (Proof Key for Code Exchange) parameters (RFC 7636)\n * Used to secure OAuth 2.0 authorization code flow for public clients\n */\nexport interface PkceParams {\n /** Code verifier - cryptographically random string (43-128 characters) */\n codeVerifier: string;\n /** Code challenge - derived from code verifier using challenge method */\n codeChallenge: string;\n /** Code challenge method - S256 (SHA-256) or plain */\n codeChallengeMethod: 'S256' | 'plain';\n}\n\n/**\n * OAuth token set with access and refresh tokens\n */\nexport interface TokenSet {\n /** Access token for API requests */\n accessToken: string;\n /** Refresh token for obtaining new access tokens */\n refreshToken: string;\n /** Timestamp when access token expires (milliseconds since epoch) */\n expiresAt: number;\n /** Scopes granted for this token set */\n scopes?: string[];\n /** Client ID used for DCR registration (stored for future use) */\n clientId?: string;\n /** Client secret used for DCR registration (stored for future use) */\n clientSecret?: string;\n}\n\n/**\n * OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Response from .well-known/oauth-protected-resource endpoint\n */\nexport interface ProtectedResourceMetadata {\n /** The protected resource identifier */\n resource: string;\n /** List of authorization server URLs that can issue tokens for this resource */\n authorization_servers: string[];\n /** Optional list of scopes supported by this resource */\n scopes_supported?: string[];\n /** Optional list of bearer token methods supported (header, query, body) */\n bearer_methods_supported?: string[];\n}\n\n/**\n * OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Response from .well-known/oauth-authorization-server endpoint\n */\nexport interface AuthorizationServerMetadata {\n /** The authorization server's issuer identifier */\n issuer?: string;\n /** URL of the authorization endpoint */\n authorization_endpoint?: string;\n /** URL of the token endpoint */\n token_endpoint?: string;\n /** URL of the client registration endpoint (DCR - RFC 7591) */\n registration_endpoint?: string;\n /** URL of the token introspection endpoint */\n introspection_endpoint?: string;\n /** List of OAuth scopes supported by the authorization server */\n scopes_supported?: string[];\n /** Response types supported (code, token, etc.) */\n response_types_supported?: string[];\n /** Grant types supported (authorization_code, refresh_token, etc.) */\n grant_types_supported?: string[];\n /** Token endpoint authentication methods supported */\n token_endpoint_auth_methods_supported?: string[];\n}\n\n/**\n * OAuth server capabilities discovered from .well-known endpoint\n */\nexport interface AuthCapabilities {\n /** Whether the server supports Dynamic Client Registration (RFC 7591) */\n supportsDcr: boolean;\n /** DCR client registration endpoint */\n registrationEndpoint?: string;\n /** OAuth authorization endpoint */\n authorizationEndpoint?: string;\n /** OAuth token endpoint */\n tokenEndpoint?: string;\n /** Token introspection endpoint */\n introspectionEndpoint?: string;\n /** Supported OAuth scopes */\n scopes?: string[];\n}\n\n/**\n * Client credentials from DCR registration\n */\nexport interface ClientCredentials {\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Timestamp when client was registered */\n issuedAt?: number;\n}\n\n/**\n * Options for DCR client registration\n */\nexport interface DcrRegistrationOptions {\n /**
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/types.ts"],"sourcesContent":["/**\n * Shared types for OAuth and DCR authentication\n */\n\n/**\n * OAuth callback result from authorization server\n */\nexport interface CallbackResult {\n /** Authorization code from OAuth server */\n code: string;\n /** State parameter for CSRF protection */\n state?: string;\n /** Issuer identifier of the authorization server that minted the response (RFC 9207) */\n iss?: string;\n}\n\n/**\n * PKCE (Proof Key for Code Exchange) parameters (RFC 7636)\n * Used to secure OAuth 2.0 authorization code flow for public clients\n */\nexport interface PkceParams {\n /** Code verifier - cryptographically random string (43-128 characters) */\n codeVerifier: string;\n /** Code challenge - derived from code verifier using challenge method */\n codeChallenge: string;\n /** Code challenge method - S256 (SHA-256) or plain */\n codeChallengeMethod: 'S256' | 'plain';\n}\n\n/**\n * OAuth token set with access and refresh tokens\n */\nexport interface TokenSet {\n /** Access token for API requests */\n accessToken: string;\n /** Refresh token for obtaining new access tokens */\n refreshToken: string;\n /** Timestamp when access token expires (milliseconds since epoch) */\n expiresAt: number;\n /** Scopes granted for this token set */\n scopes?: string[];\n /** Client ID used for DCR registration (stored for future use) */\n clientId?: string;\n /** Client secret used for DCR registration (stored for future use) */\n clientSecret?: string;\n /** Issuer identifier of the authorization server these credentials belong to (SEP-2352) */\n issuer?: string;\n}\n\n/**\n * OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Response from .well-known/oauth-protected-resource endpoint\n */\nexport interface ProtectedResourceMetadata {\n /** The protected resource identifier */\n resource: string;\n /** List of authorization server URLs that can issue tokens for this resource */\n authorization_servers: string[];\n /** Optional list of scopes supported by this resource */\n scopes_supported?: string[];\n /** Optional list of bearer token methods supported (header, query, body) */\n bearer_methods_supported?: string[];\n}\n\n/**\n * OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Response from .well-known/oauth-authorization-server endpoint\n */\nexport interface AuthorizationServerMetadata {\n /** The authorization server's issuer identifier */\n issuer?: string;\n /** URL of the authorization endpoint */\n authorization_endpoint?: string;\n /** URL of the token endpoint */\n token_endpoint?: string;\n /** URL of the client registration endpoint (DCR - RFC 7591) */\n registration_endpoint?: string;\n /** URL of the token introspection endpoint */\n introspection_endpoint?: string;\n /** List of OAuth scopes supported by the authorization server */\n scopes_supported?: string[];\n /** Response types supported (code, token, etc.) */\n response_types_supported?: string[];\n /** Grant types supported (authorization_code, refresh_token, etc.) */\n grant_types_supported?: string[];\n /** Token endpoint authentication methods supported */\n token_endpoint_auth_methods_supported?: string[];\n /** Whether the authorization response carries an `iss` parameter (RFC 9207) */\n authorization_response_iss_parameter_supported?: boolean;\n}\n\n/**\n * OAuth server capabilities discovered from .well-known endpoint\n */\nexport interface AuthCapabilities {\n /** Whether the server supports Dynamic Client Registration (RFC 7591) */\n supportsDcr: boolean;\n /** Issuer identifier from the authorization server metadata (RFC 8414) */\n issuer?: string;\n /** Whether the authorization response carries an `iss` parameter (RFC 9207) */\n authorizationResponseIssSupported?: boolean;\n /** DCR client registration endpoint */\n registrationEndpoint?: string;\n /** OAuth authorization endpoint */\n authorizationEndpoint?: string;\n /** OAuth token endpoint */\n tokenEndpoint?: string;\n /** Token introspection endpoint */\n introspectionEndpoint?: string;\n /** Supported OAuth scopes */\n scopes?: string[];\n}\n\n/**\n * Client credentials from DCR registration\n */\nexport interface ClientCredentials {\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Timestamp when client was registered */\n issuedAt?: number;\n}\n\n/**\n * Options for DCR client registration\n */\nexport interface DcrRegistrationOptions {\n /** Redirect URI for OAuth callback, from the loopback listener the caller has already bound (RFC 8252) */\n redirectUri: string;\n /** Client name to register */\n clientName?: string;\n /**\n * Loopback trust grant for the registration_endpoint fetch (SSRF\n * mitigation - see `src/auth/discovery-fetch.ts`). Compute this from the\n * MCP server the caller is actually talking to, never from\n * `registrationEndpoint` itself (which is typically sourced from\n * remote-controlled AS metadata). Defaults to `false`.\n */\n allowLoopback?: boolean;\n}\n\n/**\n * Options for OAuth authorization flow\n */\nexport interface OAuthFlowOptions {\n /** Port for OAuth callback listener (required - use get-port to find available port) */\n port: number;\n /** Issuer identifier discovered before the flow starts; the `iss` in the authorization response must match it (RFC 9207) */\n issuer: string;\n /** Canonical resource server URI, sent as `resource` on the authorization, token, and refresh requests (RFC 8707) */\n resource: string;\n /** Redirect URI for OAuth callback (optional - will be built from port if not provided) */\n redirectUri?: string;\n /** OAuth scopes to request */\n scopes?: string[];\n /** Whether the authorization server advertises `authorization_response_iss_parameter_supported` (RFC 9207) */\n authorizationResponseIssSupported?: boolean;\n /** Enable PKCE (RFC 7636) - recommended for all clients, required for public clients */\n pkce?: boolean;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Timeout for callback (milliseconds) */\n timeout?: number;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: import('../utils/logger.ts').Logger;\n /**\n * Loopback trust grant for the token endpoint fetch (SSRF mitigation - see\n * `src/auth/discovery-fetch.ts`). Compute this from the MCP server the\n * caller is actually talking to, never from `tokenEndpoint` itself (which\n * is typically sourced from remote-controlled AS metadata). Defaults to\n * `false`.\n */\n allowLoopback?: boolean;\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAED;;CAEC,GAyID;;CAEC,GACD,WA6BC"}
|
|
@@ -62,11 +62,16 @@ export declare class DcrAuthenticator {
|
|
|
62
62
|
private ensureAuthenticatedExternal;
|
|
63
63
|
/**
|
|
64
64
|
* Refreshes an access token using a refresh token.
|
|
65
|
+
* @param resource - Canonical resource server URI, audience-binding the token (RFC 8707).
|
|
65
66
|
* @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.
|
|
66
67
|
*/
|
|
67
68
|
private refreshTokens;
|
|
68
69
|
/**
|
|
69
|
-
*
|
|
70
|
+
* Deletes both stored token families for a server, across every issuer they were bound to.
|
|
71
|
+
* @throws CredentialBindingError if the configured store cannot enumerate keys.
|
|
70
72
|
*/
|
|
71
73
|
deleteTokens(baseUrl: string): Promise<void>;
|
|
74
|
+
/** Discards a stored credential that is not bound to `issuer` (SEP-2352), including one stored before issuer binding existed. */
|
|
75
|
+
private loadTokens;
|
|
76
|
+
private buildFlowOptions;
|
|
72
77
|
}
|
|
@@ -7,11 +7,27 @@ import Keyv from 'keyv';
|
|
|
7
7
|
import { KeyvFile } from 'keyv-file';
|
|
8
8
|
import { isLoopbackUrl } from '../auth/discovery-fetch.js';
|
|
9
9
|
import { InteractiveOAuthFlow } from '../auth/interactive-oauth-flow.js';
|
|
10
|
+
import { normalizeUrl } from '../lib/url-utils.js';
|
|
10
11
|
import { logger as defaultLogger } from '../utils/logger.js';
|
|
11
12
|
import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
|
|
12
13
|
/**
|
|
13
14
|
* Buffer time before token expiry to trigger proactive refresh (5 minutes)
|
|
14
15
|
*/ const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
16
|
+
/** Raised when a credential cannot be bound to, or looked up by, an issuer. */ let CredentialBindingError = class CredentialBindingError extends Error {
|
|
17
|
+
constructor(message){
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'CredentialBindingError';
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The issuer credentials are keyed by, so they can never be presented to a
|
|
24
|
+
* different authorization server even when the resource keeps its URL (SEP-2352).
|
|
25
|
+
*/ function requireIssuer(capabilities) {
|
|
26
|
+
if (!capabilities.issuer) {
|
|
27
|
+
throw new CredentialBindingError('Authorization server metadata has no issuer - credentials cannot be bound to an authorization server (RFC 8414 requires issuer)');
|
|
28
|
+
}
|
|
29
|
+
return capabilities.issuer;
|
|
30
|
+
}
|
|
15
31
|
/**
|
|
16
32
|
* DcrAuthenticator manages authentication for MCP HTTP servers
|
|
17
33
|
* Handles DCR registration, OAuth flows, and token management
|
|
@@ -61,9 +77,11 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
|
|
|
61
77
|
// Loopback trust for every discovery-derived fetch below, computed from
|
|
62
78
|
// the server we're talking to, never from capabilities' endpoints (see discovery-fetch.ts).
|
|
63
79
|
const allowLoopback = isLoopbackUrl(baseUrl);
|
|
64
|
-
const
|
|
80
|
+
const issuer = requireIssuer(capabilities);
|
|
81
|
+
const resource = normalizeUrl(baseUrl);
|
|
82
|
+
const dcrTokenKey = `dcr-tokens:${issuer}:${resource}`;
|
|
65
83
|
// 1. Check for existing DCR tokens (different from external tokens)
|
|
66
|
-
let tokens = await this.
|
|
84
|
+
let tokens = await this.loadTokens(dcrTokenKey, issuer);
|
|
67
85
|
if (tokens) {
|
|
68
86
|
// 2. Verify token is still valid by calling /oauth/verify
|
|
69
87
|
try {
|
|
@@ -102,17 +120,7 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
|
|
|
102
120
|
allowLoopback
|
|
103
121
|
});
|
|
104
122
|
// Perform OAuth authorization flow with PKCE (RFC 7636)
|
|
105
|
-
const flowOptions =
|
|
106
|
-
port,
|
|
107
|
-
headless: this.headless,
|
|
108
|
-
redirectUri: this.redirectUri,
|
|
109
|
-
pkce: true,
|
|
110
|
-
logger: this.logger,
|
|
111
|
-
allowLoopback
|
|
112
|
-
};
|
|
113
|
-
if (capabilities.scopes) {
|
|
114
|
-
flowOptions.scopes = capabilities.scopes;
|
|
115
|
-
}
|
|
123
|
+
const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback);
|
|
116
124
|
tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);
|
|
117
125
|
// For self-hosted mode, verify the token works with /oauth/verify immediately
|
|
118
126
|
try {
|
|
@@ -136,23 +144,31 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
|
|
|
136
144
|
throw new Error('Self-hosted DCR authentication completed but token verification failed');
|
|
137
145
|
}
|
|
138
146
|
// Save tokens for future use
|
|
139
|
-
await this.tokenStore.set(dcrTokenKey,
|
|
147
|
+
await this.tokenStore.set(dcrTokenKey, {
|
|
148
|
+
...tokens,
|
|
149
|
+
issuer
|
|
150
|
+
});
|
|
140
151
|
this.logger.debug('✅ Self-hosted DCR authentication successful, tokens saved');
|
|
141
152
|
return tokens;
|
|
142
153
|
}
|
|
143
154
|
/** Handles authentication for external (non-self-hosted) OAuth providers. */ async ensureAuthenticatedExternal(baseUrl, capabilities) {
|
|
144
155
|
// See ensureAuthenticatedSelfHosted - same loopback trust rule.
|
|
145
156
|
const allowLoopback = isLoopbackUrl(baseUrl);
|
|
146
|
-
const
|
|
157
|
+
const issuer = requireIssuer(capabilities);
|
|
158
|
+
const resource = normalizeUrl(baseUrl);
|
|
159
|
+
const tokenKey = `tokens:${issuer}:${resource}`;
|
|
147
160
|
// 1. Check for existing tokens
|
|
148
|
-
let tokens = await this.
|
|
161
|
+
let tokens = await this.loadTokens(tokenKey, issuer);
|
|
149
162
|
if (tokens) {
|
|
150
163
|
// 2. Proactive refresh if token expires within 5 minutes
|
|
151
164
|
if (tokens.expiresAt < Date.now() + REFRESH_BUFFER_MS) {
|
|
152
165
|
this.logger.debug('🔄 Refreshing access token...');
|
|
153
166
|
try {
|
|
154
|
-
tokens = await this.refreshTokens(tokens, capabilities.tokenEndpoint, allowLoopback);
|
|
155
|
-
await this.tokenStore.set(tokenKey,
|
|
167
|
+
tokens = await this.refreshTokens(tokens, capabilities.tokenEndpoint, resource, allowLoopback);
|
|
168
|
+
await this.tokenStore.set(tokenKey, {
|
|
169
|
+
...tokens,
|
|
170
|
+
issuer
|
|
171
|
+
});
|
|
156
172
|
this.logger.debug('✅ Token refreshed successfully');
|
|
157
173
|
} catch (_error) {
|
|
158
174
|
// Refresh failed - clear tokens and re-authenticate
|
|
@@ -179,27 +195,21 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
|
|
|
179
195
|
allowLoopback
|
|
180
196
|
});
|
|
181
197
|
// Perform OAuth authorization flow with PKCE (RFC 7636)
|
|
182
|
-
const flowOptions =
|
|
183
|
-
port,
|
|
184
|
-
headless: this.headless,
|
|
185
|
-
redirectUri: this.redirectUri,
|
|
186
|
-
pkce: true,
|
|
187
|
-
logger: this.logger,
|
|
188
|
-
allowLoopback
|
|
189
|
-
};
|
|
190
|
-
if (capabilities.scopes) {
|
|
191
|
-
flowOptions.scopes = capabilities.scopes;
|
|
192
|
-
}
|
|
198
|
+
const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback);
|
|
193
199
|
tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);
|
|
194
200
|
// Save tokens for future use
|
|
195
|
-
await this.tokenStore.set(tokenKey,
|
|
201
|
+
await this.tokenStore.set(tokenKey, {
|
|
202
|
+
...tokens,
|
|
203
|
+
issuer
|
|
204
|
+
});
|
|
196
205
|
this.logger.debug('✅ Authentication successful, tokens saved');
|
|
197
206
|
return tokens;
|
|
198
207
|
}
|
|
199
208
|
/**
|
|
200
209
|
* Refreshes an access token using a refresh token.
|
|
210
|
+
* @param resource - Canonical resource server URI, audience-binding the token (RFC 8707).
|
|
201
211
|
* @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.
|
|
202
|
-
*/ async refreshTokens(tokens, tokenEndpoint, allowLoopback = false) {
|
|
212
|
+
*/ async refreshTokens(tokens, tokenEndpoint, resource, allowLoopback = false) {
|
|
203
213
|
if (!tokenEndpoint) {
|
|
204
214
|
throw new Error('Token endpoint not available for refresh');
|
|
205
215
|
}
|
|
@@ -209,15 +219,49 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
|
|
|
209
219
|
if (!tokens.clientId || !tokens.clientSecret) {
|
|
210
220
|
throw new Error('Client credentials not available for refresh');
|
|
211
221
|
}
|
|
212
|
-
return await this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, allowLoopback);
|
|
222
|
+
return await this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, resource, allowLoopback);
|
|
213
223
|
}
|
|
214
224
|
/**
|
|
215
|
-
*
|
|
225
|
+
* Deletes both stored token families for a server, across every issuer they were bound to.
|
|
226
|
+
* @throws CredentialBindingError if the configured store cannot enumerate keys.
|
|
216
227
|
*/ async deleteTokens(baseUrl) {
|
|
217
|
-
const
|
|
218
|
-
|
|
228
|
+
const suffix = `:${normalizeUrl(baseUrl)}`;
|
|
229
|
+
if (!this.tokenStore.iterator) {
|
|
230
|
+
throw new CredentialBindingError('Token store does not support key enumeration, which issuer-keyed credentials require');
|
|
231
|
+
}
|
|
232
|
+
for await (const [key] of this.tokenStore.iterator(this.tokenStore.namespace)){
|
|
233
|
+
if (typeof key === 'string' && (key.startsWith('tokens:') || key.startsWith('dcr-tokens:')) && key.endsWith(suffix)) {
|
|
234
|
+
await this.tokenStore.delete(key);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
219
237
|
this.logger.debug(`🗑️ Deleted tokens for ${baseUrl}`);
|
|
220
238
|
}
|
|
239
|
+
/** Discards a stored credential that is not bound to `issuer` (SEP-2352), including one stored before issuer binding existed. */ async loadTokens(key, issuer) {
|
|
240
|
+
const tokens = await this.tokenStore.get(key);
|
|
241
|
+
if (!tokens) return undefined;
|
|
242
|
+
if (tokens.issuer === issuer) return tokens;
|
|
243
|
+
this.logger.debug('🔑 Stored credential is not bound to the discovered issuer, re-authorizing');
|
|
244
|
+
await this.tokenStore.delete(key);
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
buildFlowOptions(port, capabilities, issuer, resource, allowLoopback) {
|
|
248
|
+
var _capabilities_authorizationResponseIssSupported;
|
|
249
|
+
const flowOptions = {
|
|
250
|
+
port,
|
|
251
|
+
issuer,
|
|
252
|
+
resource,
|
|
253
|
+
headless: this.headless,
|
|
254
|
+
redirectUri: this.redirectUri,
|
|
255
|
+
pkce: true,
|
|
256
|
+
logger: this.logger,
|
|
257
|
+
allowLoopback,
|
|
258
|
+
authorizationResponseIssSupported: (_capabilities_authorizationResponseIssSupported = capabilities.authorizationResponseIssSupported) !== null && _capabilities_authorizationResponseIssSupported !== void 0 ? _capabilities_authorizationResponseIssSupported : false
|
|
259
|
+
};
|
|
260
|
+
if (capabilities.scopes) {
|
|
261
|
+
flowOptions.scopes = capabilities.scopes;
|
|
262
|
+
}
|
|
263
|
+
return flowOptions;
|
|
264
|
+
}
|
|
221
265
|
constructor(options){
|
|
222
266
|
var _options_logger;
|
|
223
267
|
if (options.tokenStore) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/dcr/dcr-authenticator.ts"],"sourcesContent":["/**\n * DCR Authenticator\n * Consolidates DCR and OAuth flow logic for MCP HTTP servers\n */\n\nimport path from 'node:path';\nimport * as fs from 'fs';\nimport Keyv from 'keyv';\nimport { KeyvFile } from 'keyv-file';\nimport { isLoopbackUrl } from '../auth/discovery-fetch.ts';\nimport { InteractiveOAuthFlow } from '../auth/interactive-oauth-flow.ts';\nimport type { AuthCapabilities, TokenSet } from '../auth/types.ts';\nimport { logger as defaultLogger, type Logger } from '../utils/logger.ts';\nimport { DynamicClientRegistrar } from './dynamic-client-registrar.ts';\n\n/**\n * DcrAuthenticator configuration options\n */\nexport interface DcrAuthenticatorOptions {\n /** Custom Keyv store (for testing) - if not provided, uses default ~/.mcpeasy/tokens.json */\n tokenStore?: Keyv;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Required redirect URI for OAuth callback */\n redirectUri: string;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: Logger;\n}\n\n/**\n * Buffer time before token expiry to trigger proactive refresh (5 minutes)\n */\nconst REFRESH_BUFFER_MS = 5 * 60 * 1000;\n\n/**\n * DcrAuthenticator manages authentication for MCP HTTP servers\n * Handles DCR registration, OAuth flows, and token management\n */\nexport class DcrAuthenticator {\n private tokenStore: Keyv;\n private dcrClient: DynamicClientRegistrar;\n private oauthFlow: InteractiveOAuthFlow;\n private headless: boolean;\n private redirectUri: string;\n private logger: Logger;\n\n constructor(options: DcrAuthenticatorOptions) {\n if (options.tokenStore) {\n this.tokenStore = options.tokenStore;\n } else {\n // Default CLI store in .mcp-z directory (per-project)\n const storePath = path.join(process.cwd(), '.mcp-z', 'tokens.json');\n\n // Ensure directory exists before creating store\n fs.mkdirSync(path.dirname(storePath), { recursive: true });\n\n this.tokenStore = new Keyv({\n store: new KeyvFile({ filename: storePath }),\n });\n }\n this.dcrClient = new DynamicClientRegistrar();\n this.oauthFlow = new InteractiveOAuthFlow();\n this.headless = options.headless || false;\n this.redirectUri = options.redirectUri;\n this.logger = options.logger ?? defaultLogger;\n }\n\n /**\n * Detect if server is self-hosted DCR (vs external OAuth provider)\n * Self-hosted servers have their own OAuth endpoints and manage token storage\n */\n private async detectSelfHostedMode(baseUrl: string): Promise<boolean> {\n try {\n // Self-hosted DCR servers typically run their own OAuth server\n // Check if this is a self-hosted instance by testing OAuth metadata\n // For now, assume self-hosted if baseUrl matches common localhost patterns\n // TODO: Implement proper self-hosted detection logic\n return baseUrl.includes('localhost') || baseUrl.includes('127.0.0.1');\n } catch (_error) {\n return false; // Assume external mode if detection fails\n }\n }\n\n /**\n * Ensure server is authenticated, performing DCR and OAuth if needed\n * Proactively refreshes tokens if they're within 5 minutes of expiry\n *\n * @param baseUrl - Base URL of the server (e.g., https://example.com)\n * @param capabilities - Auth capabilities from .well-known endpoint\n * @returns Valid token set ready to use\n *\n * @throws Error if authentication fails\n *\n * @example\n * const authenticator = new DcrAuthenticator({ redirectUri: 'http://localhost:3000/callback' });\n * const tokens = await authenticator.ensureAuthenticated(\n * 'https://example.com',\n * capabilities\n * );\n */\n async ensureAuthenticated(baseUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // Auto-detect server mode\n const isSelfHosted = await this.detectSelfHostedMode(baseUrl);\n\n if (isSelfHosted) {\n return this.ensureAuthenticatedSelfHosted(baseUrl, capabilities);\n }\n return this.ensureAuthenticatedExternal(baseUrl, capabilities);\n }\n\n /**\n * Handle authentication for self-hosted DCR servers\n * Self-hosted servers manage their own token storage via /oauth/verify\n */\n private async ensureAuthenticatedSelfHosted(baseUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // Loopback trust for every discovery-derived fetch below, computed from\n // the server we're talking to, never from capabilities' endpoints (see discovery-fetch.ts).\n const allowLoopback = isLoopbackUrl(baseUrl);\n const dcrTokenKey = `dcr-tokens:${baseUrl}`;\n\n // 1. Check for existing DCR tokens (different from external tokens)\n let tokens = (await this.tokenStore.get(dcrTokenKey)) as TokenSet | undefined;\n\n if (tokens) {\n // 2. Verify token is still valid by calling /oauth/verify\n try {\n const verifyUrl = `${baseUrl}/oauth/verify`;\n const verifyResponse = await fetch(verifyUrl, {\n headers: { Authorization: `Bearer ${tokens.accessToken}`, Connection: 'close' },\n });\n\n if (verifyResponse.ok) {\n const verifyData = (await verifyResponse.json()) as { token?: string };\n if (verifyData.token === tokens.accessToken) {\n // Token is still valid with the self-hosted server\n return tokens;\n }\n }\n } catch (_error) {\n // Token verification failed - need to re-authenticate\n }\n\n // Token is expired or invalid\n await this.tokenStore.delete(dcrTokenKey);\n tokens = undefined;\n }\n\n // 3. No valid tokens - perform full DCR + OAuth flow\n if (!capabilities.registrationEndpoint || !capabilities.authorizationEndpoint || !capabilities.tokenEndpoint) {\n throw new Error('Server does not provide required OAuth endpoints');\n }\n\n this.logger.debug('🔐 No valid tokens found, starting self-hosted DCR authentication...');\n\n // Extract port from pre-resolved redirectUri\n const port = parseInt(new URL(this.redirectUri).port, 10) || (this.redirectUri.startsWith('https:') ? 443 : 80);\n\n // Register OAuth client via DCR\n this.logger.debug('📝 Registering OAuth client with self-hosted server...');\n const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {\n redirectUri: this.redirectUri,\n allowLoopback,\n });\n\n // Perform OAuth authorization flow with PKCE (RFC 7636)\n const flowOptions: { port: number; headless: boolean; scopes?: string[]; redirectUri: string; pkce: boolean; logger: Logger; allowLoopback: boolean } = {\n port,\n headless: this.headless,\n redirectUri: this.redirectUri,\n pkce: true,\n logger: this.logger,\n allowLoopback,\n };\n if (capabilities.scopes) {\n flowOptions.scopes = capabilities.scopes;\n }\n\n tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);\n\n // For self-hosted mode, verify the token works with /oauth/verify immediately\n try {\n const verifyUrl = `${baseUrl}/oauth/verify`;\n const verifyResponse = await fetch(verifyUrl, {\n headers: { Authorization: `Bearer ${tokens.accessToken}`, Connection: 'close' },\n });\n\n if (!verifyResponse.ok) {\n throw new Error(`DCR token verification failed after authentication: ${verifyResponse.status}`);\n }\n\n const verifyData = (await verifyResponse.json()) as { token?: string };\n if (verifyData.token !== tokens.accessToken) {\n throw new Error('DCR server returned different token in verification');\n }\n\n this.logger.debug('✅ DCR token verified with self-hosted server');\n } catch (error) {\n this.logger.error('❌ DCR token verification failed:', error instanceof Error ? error.message : String(error));\n throw new Error('Self-hosted DCR authentication completed but token verification failed');\n }\n\n // Save tokens for future use\n await this.tokenStore.set(dcrTokenKey, tokens);\n this.logger.debug('✅ Self-hosted DCR authentication successful, tokens saved');\n\n return tokens;\n }\n\n /** Handles authentication for external (non-self-hosted) OAuth providers. */\n private async ensureAuthenticatedExternal(baseUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // See ensureAuthenticatedSelfHosted - same loopback trust rule.\n const allowLoopback = isLoopbackUrl(baseUrl);\n const tokenKey = `tokens:${baseUrl}`;\n\n // 1. Check for existing tokens\n let tokens = (await this.tokenStore.get(tokenKey)) as TokenSet | undefined;\n\n if (tokens) {\n // 2. Proactive refresh if token expires within 5 minutes\n if (tokens.expiresAt < Date.now() + REFRESH_BUFFER_MS) {\n this.logger.debug('🔄 Refreshing access token...');\n\n try {\n tokens = await this.refreshTokens(tokens, capabilities.tokenEndpoint, allowLoopback);\n await this.tokenStore.set(tokenKey, tokens);\n this.logger.debug('✅ Token refreshed successfully');\n } catch (_error) {\n // Refresh failed - clear tokens and re-authenticate\n this.logger.warn('⚠️ Token refresh failed, re-authenticating...');\n await this.tokenStore.delete(tokenKey);\n tokens = undefined;\n }\n }\n\n if (tokens) {\n return tokens;\n }\n }\n\n // 3. No valid tokens - perform DCR + OAuth flow\n if (!capabilities.registrationEndpoint || !capabilities.authorizationEndpoint || !capabilities.tokenEndpoint) {\n throw new Error('Server does not provide required OAuth endpoints');\n }\n\n this.logger.debug('🔐 No valid tokens found, starting external OAuth authentication...');\n\n // Extract port from pre-resolved redirectUri\n const port = parseInt(new URL(this.redirectUri).port, 10) || (this.redirectUri.startsWith('https:') ? 443 : 80);\n\n // Register OAuth client via DCR\n this.logger.debug('📝 Registering OAuth client...');\n const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {\n redirectUri: this.redirectUri,\n allowLoopback,\n });\n\n // Perform OAuth authorization flow with PKCE (RFC 7636)\n const flowOptions: { port: number; headless: boolean; scopes?: string[]; redirectUri: string; pkce: boolean; logger: Logger; allowLoopback: boolean } = {\n port,\n headless: this.headless,\n redirectUri: this.redirectUri,\n pkce: true,\n logger: this.logger,\n allowLoopback,\n };\n if (capabilities.scopes) {\n flowOptions.scopes = capabilities.scopes;\n }\n\n tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);\n\n // Save tokens for future use\n await this.tokenStore.set(tokenKey, tokens);\n this.logger.debug('✅ Authentication successful, tokens saved');\n\n return tokens;\n }\n\n /**\n * Refreshes an access token using a refresh token.\n * @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.\n */\n private async refreshTokens(tokens: TokenSet, tokenEndpoint: string | undefined, allowLoopback = false): Promise<TokenSet> {\n if (!tokenEndpoint) {\n throw new Error('Token endpoint not available for refresh');\n }\n\n if (!tokens.refreshToken) {\n throw new Error('No refresh token available');\n }\n\n if (!tokens.clientId || !tokens.clientSecret) {\n throw new Error('Client credentials not available for refresh');\n }\n\n return await this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, allowLoopback);\n }\n\n /**\n * Delete stored tokens for a server\n */\n async deleteTokens(baseUrl: string): Promise<void> {\n const tokenKey = `tokens:${baseUrl}`;\n await this.tokenStore.delete(tokenKey);\n this.logger.debug(`🗑️ Deleted tokens for ${baseUrl}`);\n }\n}\n"],"names":["path","fs","Keyv","KeyvFile","isLoopbackUrl","InteractiveOAuthFlow","logger","defaultLogger","DynamicClientRegistrar","REFRESH_BUFFER_MS","DcrAuthenticator","detectSelfHostedMode","baseUrl","includes","_error","ensureAuthenticated","capabilities","isSelfHosted","ensureAuthenticatedSelfHosted","ensureAuthenticatedExternal","allowLoopback","dcrTokenKey","tokens","tokenStore","get","verifyUrl","verifyResponse","fetch","headers","Authorization","accessToken","Connection","ok","verifyData","json","token","delete","undefined","registrationEndpoint","authorizationEndpoint","tokenEndpoint","Error","debug","port","parseInt","URL","redirectUri","startsWith","client","dcrClient","registerClient","flowOptions","headless","pkce","scopes","oauthFlow","performAuthFlow","clientId","clientSecret","status","error","message","String","set","tokenKey","expiresAt","Date","now","refreshTokens","warn","refreshToken","deleteTokens","options","storePath","join","process","cwd","mkdirSync","dirname","recursive","store","filename"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/dcr/dcr-authenticator.ts"],"sourcesContent":["/**\n * DCR Authenticator\n * Consolidates DCR and OAuth flow logic for MCP HTTP servers\n */\n\nimport path from 'node:path';\nimport * as fs from 'fs';\nimport Keyv from 'keyv';\nimport { KeyvFile } from 'keyv-file';\nimport { isLoopbackUrl } from '../auth/discovery-fetch.ts';\nimport { InteractiveOAuthFlow } from '../auth/interactive-oauth-flow.ts';\nimport type { AuthCapabilities, OAuthFlowOptions, TokenSet } from '../auth/types.ts';\nimport { normalizeUrl } from '../lib/url-utils.ts';\nimport { logger as defaultLogger, type Logger } from '../utils/logger.ts';\nimport { DynamicClientRegistrar } from './dynamic-client-registrar.ts';\n\n/**\n * DcrAuthenticator configuration options\n */\nexport interface DcrAuthenticatorOptions {\n /** Custom Keyv store (for testing) - if not provided, uses default ~/.mcpeasy/tokens.json */\n tokenStore?: Keyv;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Required redirect URI for OAuth callback */\n redirectUri: string;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: Logger;\n}\n\n/**\n * Buffer time before token expiry to trigger proactive refresh (5 minutes)\n */\nconst REFRESH_BUFFER_MS = 5 * 60 * 1000;\n\n/** Raised when a credential cannot be bound to, or looked up by, an issuer. */\nclass CredentialBindingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CredentialBindingError';\n }\n}\n\n/**\n * The issuer credentials are keyed by, so they can never be presented to a\n * different authorization server even when the resource keeps its URL (SEP-2352).\n */\nfunction requireIssuer(capabilities: AuthCapabilities): string {\n if (!capabilities.issuer) {\n throw new CredentialBindingError('Authorization server metadata has no issuer - credentials cannot be bound to an authorization server (RFC 8414 requires issuer)');\n }\n return capabilities.issuer;\n}\n\n/**\n * DcrAuthenticator manages authentication for MCP HTTP servers\n * Handles DCR registration, OAuth flows, and token management\n */\nexport class DcrAuthenticator {\n private tokenStore: Keyv;\n private dcrClient: DynamicClientRegistrar;\n private oauthFlow: InteractiveOAuthFlow;\n private headless: boolean;\n private redirectUri: string;\n private logger: Logger;\n\n constructor(options: DcrAuthenticatorOptions) {\n if (options.tokenStore) {\n this.tokenStore = options.tokenStore;\n } else {\n // Default CLI store in .mcp-z directory (per-project)\n const storePath = path.join(process.cwd(), '.mcp-z', 'tokens.json');\n\n // Ensure directory exists before creating store\n fs.mkdirSync(path.dirname(storePath), { recursive: true });\n\n this.tokenStore = new Keyv({\n store: new KeyvFile({ filename: storePath }),\n });\n }\n this.dcrClient = new DynamicClientRegistrar();\n this.oauthFlow = new InteractiveOAuthFlow();\n this.headless = options.headless || false;\n this.redirectUri = options.redirectUri;\n this.logger = options.logger ?? defaultLogger;\n }\n\n /**\n * Detect if server is self-hosted DCR (vs external OAuth provider)\n * Self-hosted servers have their own OAuth endpoints and manage token storage\n */\n private async detectSelfHostedMode(baseUrl: string): Promise<boolean> {\n try {\n // Self-hosted DCR servers typically run their own OAuth server\n // Check if this is a self-hosted instance by testing OAuth metadata\n // For now, assume self-hosted if baseUrl matches common localhost patterns\n // TODO: Implement proper self-hosted detection logic\n return baseUrl.includes('localhost') || baseUrl.includes('127.0.0.1');\n } catch (_error) {\n return false; // Assume external mode if detection fails\n }\n }\n\n /**\n * Ensure server is authenticated, performing DCR and OAuth if needed\n * Proactively refreshes tokens if they're within 5 minutes of expiry\n *\n * @param baseUrl - Base URL of the server (e.g., https://example.com)\n * @param capabilities - Auth capabilities from .well-known endpoint\n * @returns Valid token set ready to use\n *\n * @throws Error if authentication fails\n *\n * @example\n * const authenticator = new DcrAuthenticator({ redirectUri: 'http://localhost:3000/callback' });\n * const tokens = await authenticator.ensureAuthenticated(\n * 'https://example.com',\n * capabilities\n * );\n */\n async ensureAuthenticated(baseUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // Auto-detect server mode\n const isSelfHosted = await this.detectSelfHostedMode(baseUrl);\n\n if (isSelfHosted) {\n return this.ensureAuthenticatedSelfHosted(baseUrl, capabilities);\n }\n return this.ensureAuthenticatedExternal(baseUrl, capabilities);\n }\n\n /**\n * Handle authentication for self-hosted DCR servers\n * Self-hosted servers manage their own token storage via /oauth/verify\n */\n private async ensureAuthenticatedSelfHosted(baseUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // Loopback trust for every discovery-derived fetch below, computed from\n // the server we're talking to, never from capabilities' endpoints (see discovery-fetch.ts).\n const allowLoopback = isLoopbackUrl(baseUrl);\n const issuer = requireIssuer(capabilities);\n const resource = normalizeUrl(baseUrl);\n const dcrTokenKey = `dcr-tokens:${issuer}:${resource}`;\n\n // 1. Check for existing DCR tokens (different from external tokens)\n let tokens = await this.loadTokens(dcrTokenKey, issuer);\n\n if (tokens) {\n // 2. Verify token is still valid by calling /oauth/verify\n try {\n const verifyUrl = `${baseUrl}/oauth/verify`;\n const verifyResponse = await fetch(verifyUrl, {\n headers: { Authorization: `Bearer ${tokens.accessToken}`, Connection: 'close' },\n });\n\n if (verifyResponse.ok) {\n const verifyData = (await verifyResponse.json()) as { token?: string };\n if (verifyData.token === tokens.accessToken) {\n // Token is still valid with the self-hosted server\n return tokens;\n }\n }\n } catch (_error) {\n // Token verification failed - need to re-authenticate\n }\n\n // Token is expired or invalid\n await this.tokenStore.delete(dcrTokenKey);\n tokens = undefined;\n }\n\n // 3. No valid tokens - perform full DCR + OAuth flow\n if (!capabilities.registrationEndpoint || !capabilities.authorizationEndpoint || !capabilities.tokenEndpoint) {\n throw new Error('Server does not provide required OAuth endpoints');\n }\n\n this.logger.debug('🔐 No valid tokens found, starting self-hosted DCR authentication...');\n\n // Extract port from pre-resolved redirectUri\n const port = parseInt(new URL(this.redirectUri).port, 10) || (this.redirectUri.startsWith('https:') ? 443 : 80);\n\n // Register OAuth client via DCR\n this.logger.debug('📝 Registering OAuth client with self-hosted server...');\n const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {\n redirectUri: this.redirectUri,\n allowLoopback,\n });\n\n // Perform OAuth authorization flow with PKCE (RFC 7636)\n const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback);\n\n tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);\n\n // For self-hosted mode, verify the token works with /oauth/verify immediately\n try {\n const verifyUrl = `${baseUrl}/oauth/verify`;\n const verifyResponse = await fetch(verifyUrl, {\n headers: { Authorization: `Bearer ${tokens.accessToken}`, Connection: 'close' },\n });\n\n if (!verifyResponse.ok) {\n throw new Error(`DCR token verification failed after authentication: ${verifyResponse.status}`);\n }\n\n const verifyData = (await verifyResponse.json()) as { token?: string };\n if (verifyData.token !== tokens.accessToken) {\n throw new Error('DCR server returned different token in verification');\n }\n\n this.logger.debug('✅ DCR token verified with self-hosted server');\n } catch (error) {\n this.logger.error('❌ DCR token verification failed:', error instanceof Error ? error.message : String(error));\n throw new Error('Self-hosted DCR authentication completed but token verification failed');\n }\n\n // Save tokens for future use\n await this.tokenStore.set(dcrTokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Self-hosted DCR authentication successful, tokens saved');\n\n return tokens;\n }\n\n /** Handles authentication for external (non-self-hosted) OAuth providers. */\n private async ensureAuthenticatedExternal(baseUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // See ensureAuthenticatedSelfHosted - same loopback trust rule.\n const allowLoopback = isLoopbackUrl(baseUrl);\n const issuer = requireIssuer(capabilities);\n const resource = normalizeUrl(baseUrl);\n const tokenKey = `tokens:${issuer}:${resource}`;\n\n // 1. Check for existing tokens\n let tokens = await this.loadTokens(tokenKey, issuer);\n\n if (tokens) {\n // 2. Proactive refresh if token expires within 5 minutes\n if (tokens.expiresAt < Date.now() + REFRESH_BUFFER_MS) {\n this.logger.debug('🔄 Refreshing access token...');\n\n try {\n tokens = await this.refreshTokens(tokens, capabilities.tokenEndpoint, resource, allowLoopback);\n await this.tokenStore.set(tokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Token refreshed successfully');\n } catch (_error) {\n // Refresh failed - clear tokens and re-authenticate\n this.logger.warn('⚠️ Token refresh failed, re-authenticating...');\n await this.tokenStore.delete(tokenKey);\n tokens = undefined;\n }\n }\n\n if (tokens) {\n return tokens;\n }\n }\n\n // 3. No valid tokens - perform DCR + OAuth flow\n if (!capabilities.registrationEndpoint || !capabilities.authorizationEndpoint || !capabilities.tokenEndpoint) {\n throw new Error('Server does not provide required OAuth endpoints');\n }\n\n this.logger.debug('🔐 No valid tokens found, starting external OAuth authentication...');\n\n // Extract port from pre-resolved redirectUri\n const port = parseInt(new URL(this.redirectUri).port, 10) || (this.redirectUri.startsWith('https:') ? 443 : 80);\n\n // Register OAuth client via DCR\n this.logger.debug('📝 Registering OAuth client...');\n const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {\n redirectUri: this.redirectUri,\n allowLoopback,\n });\n\n // Perform OAuth authorization flow with PKCE (RFC 7636)\n const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback);\n\n tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);\n\n // Save tokens for future use\n await this.tokenStore.set(tokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Authentication successful, tokens saved');\n\n return tokens;\n }\n\n /**\n * Refreshes an access token using a refresh token.\n * @param resource - Canonical resource server URI, audience-binding the token (RFC 8707).\n * @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.\n */\n private async refreshTokens(tokens: TokenSet, tokenEndpoint: string | undefined, resource: string, allowLoopback = false): Promise<TokenSet> {\n if (!tokenEndpoint) {\n throw new Error('Token endpoint not available for refresh');\n }\n\n if (!tokens.refreshToken) {\n throw new Error('No refresh token available');\n }\n\n if (!tokens.clientId || !tokens.clientSecret) {\n throw new Error('Client credentials not available for refresh');\n }\n\n return await this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, resource, allowLoopback);\n }\n\n /**\n * Deletes both stored token families for a server, across every issuer they were bound to.\n * @throws CredentialBindingError if the configured store cannot enumerate keys.\n */\n async deleteTokens(baseUrl: string): Promise<void> {\n const suffix = `:${normalizeUrl(baseUrl)}`;\n if (!this.tokenStore.iterator) {\n throw new CredentialBindingError('Token store does not support key enumeration, which issuer-keyed credentials require');\n }\n\n for await (const [key] of this.tokenStore.iterator(this.tokenStore.namespace)) {\n if (typeof key === 'string' && (key.startsWith('tokens:') || key.startsWith('dcr-tokens:')) && key.endsWith(suffix)) {\n await this.tokenStore.delete(key);\n }\n }\n this.logger.debug(`🗑️ Deleted tokens for ${baseUrl}`);\n }\n\n /** Discards a stored credential that is not bound to `issuer` (SEP-2352), including one stored before issuer binding existed. */\n private async loadTokens(key: string, issuer: string): Promise<TokenSet | undefined> {\n const tokens = (await this.tokenStore.get(key)) as TokenSet | undefined;\n if (!tokens) return undefined;\n if (tokens.issuer === issuer) return tokens;\n\n this.logger.debug('🔑 Stored credential is not bound to the discovered issuer, re-authorizing');\n await this.tokenStore.delete(key);\n return undefined;\n }\n\n private buildFlowOptions(port: number, capabilities: AuthCapabilities, issuer: string, resource: string, allowLoopback: boolean): OAuthFlowOptions {\n const flowOptions: OAuthFlowOptions = {\n port,\n issuer,\n resource,\n headless: this.headless,\n redirectUri: this.redirectUri,\n pkce: true,\n logger: this.logger,\n allowLoopback,\n authorizationResponseIssSupported: capabilities.authorizationResponseIssSupported ?? false,\n };\n if (capabilities.scopes) {\n flowOptions.scopes = capabilities.scopes;\n }\n return flowOptions;\n }\n}\n"],"names":["path","fs","Keyv","KeyvFile","isLoopbackUrl","InteractiveOAuthFlow","normalizeUrl","logger","defaultLogger","DynamicClientRegistrar","REFRESH_BUFFER_MS","CredentialBindingError","Error","message","name","requireIssuer","capabilities","issuer","DcrAuthenticator","detectSelfHostedMode","baseUrl","includes","_error","ensureAuthenticated","isSelfHosted","ensureAuthenticatedSelfHosted","ensureAuthenticatedExternal","allowLoopback","resource","dcrTokenKey","tokens","loadTokens","verifyUrl","verifyResponse","fetch","headers","Authorization","accessToken","Connection","ok","verifyData","json","token","tokenStore","delete","undefined","registrationEndpoint","authorizationEndpoint","tokenEndpoint","debug","port","parseInt","URL","redirectUri","startsWith","client","dcrClient","registerClient","flowOptions","buildFlowOptions","oauthFlow","performAuthFlow","clientId","clientSecret","status","error","String","set","tokenKey","expiresAt","Date","now","refreshTokens","warn","refreshToken","deleteTokens","suffix","iterator","key","namespace","endsWith","get","headless","pkce","authorizationResponseIssSupported","scopes","options","storePath","join","process","cwd","mkdirSync","dirname","recursive","store","filename"],"mappings":"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"}
|
|
@@ -9,9 +9,9 @@ export declare class DynamicClientRegistrar {
|
|
|
9
9
|
* Registers a new OAuth client with the authorization server (RFC 7591).
|
|
10
10
|
*
|
|
11
11
|
* @param registrationEndpoint - Often sourced from remote-controlled AS metadata; pass `options.allowLoopback` explicitly. Defaults to `false`.
|
|
12
|
-
* @param options - Registration options (
|
|
12
|
+
* @param options - Registration options (redirect URI, client name, loopback trust).
|
|
13
13
|
* @returns Client credentials (client ID and secret).
|
|
14
14
|
* @throws Error if registration fails or the server returns an error.
|
|
15
15
|
*/
|
|
16
|
-
registerClient(registrationEndpoint: string, options
|
|
16
|
+
registerClient(registrationEndpoint: string, options: DcrRegistrationOptions): Promise<ClientCredentials>;
|
|
17
17
|
}
|
|
@@ -7,17 +7,15 @@
|
|
|
7
7
|
* Registers a new OAuth client with the authorization server (RFC 7591).
|
|
8
8
|
*
|
|
9
9
|
* @param registrationEndpoint - Often sourced from remote-controlled AS metadata; pass `options.allowLoopback` explicitly. Defaults to `false`.
|
|
10
|
-
* @param options - Registration options (
|
|
10
|
+
* @param options - Registration options (redirect URI, client name, loopback trust).
|
|
11
11
|
* @returns Client credentials (client ID and secret).
|
|
12
12
|
* @throws Error if registration fails or the server returns an error.
|
|
13
|
-
*/ async registerClient(registrationEndpoint, options
|
|
13
|
+
*/ async registerClient(registrationEndpoint, options) {
|
|
14
14
|
var _options_allowLoopback;
|
|
15
15
|
const requestBody = {
|
|
16
16
|
client_name: options.clientName || '@mcp-z/client',
|
|
17
|
-
redirect_uris:
|
|
17
|
+
redirect_uris: [
|
|
18
18
|
options.redirectUri
|
|
19
|
-
] : [
|
|
20
|
-
'http://localhost:3000/callback'
|
|
21
19
|
],
|
|
22
20
|
grant_types: [
|
|
23
21
|
'authorization_code',
|
|
@@ -26,7 +24,10 @@
|
|
|
26
24
|
response_types: [
|
|
27
25
|
'code'
|
|
28
26
|
],
|
|
29
|
-
token_endpoint_auth_method: 'client_secret_basic'
|
|
27
|
+
token_endpoint_auth_method: 'client_secret_basic',
|
|
28
|
+
// The callback listener always binds an OS-assigned loopback port, so the
|
|
29
|
+
// redirect URI is one an authorization server rejects for a web client (SEP-837, RFC 8252).
|
|
30
|
+
application_type: 'native'
|
|
30
31
|
};
|
|
31
32
|
// registrationEndpoint is remote-controlled discovery data; allowLoopback
|
|
32
33
|
// must come from the caller's own trust, never from registrationEndpoint.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/dcr/dynamic-client-registrar.ts"],"sourcesContent":["/**\n * Dynamic Client Registration (DCR) Client\n * Implements RFC 7591 for OAuth client registration\n */\n\nimport { discoveryFetch } from '../auth/discovery-fetch.ts';\nimport type { ClientCredentials, DcrRegistrationOptions } from '../auth/types.ts';\n\n/** DCR Registration Request (RFC 7591) */\ninterface DcrRegistrationRequest {\n client_name?: string;\n redirect_uris?: string[];\n grant_types?: string[];\n response_types?: string[];\n token_endpoint_auth_method?: string;\n}\n\n/** DCR Registration Response (RFC 7591) */\ninterface DcrRegistrationResponse {\n client_id: string;\n client_secret?: string;\n client_id_issued_at?: number;\n client_secret_expires_at?: number;\n}\n\n/** Handles Dynamic Client Registration with OAuth servers. */\nexport class DynamicClientRegistrar {\n /**\n * Registers a new OAuth client with the authorization server (RFC 7591).\n *\n * @param registrationEndpoint - Often sourced from remote-controlled AS metadata; pass `options.allowLoopback` explicitly. Defaults to `false`.\n * @param options - Registration options (
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/dcr/dynamic-client-registrar.ts"],"sourcesContent":["/**\n * Dynamic Client Registration (DCR) Client\n * Implements RFC 7591 for OAuth client registration\n */\n\nimport { discoveryFetch } from '../auth/discovery-fetch.ts';\nimport type { ClientCredentials, DcrRegistrationOptions } from '../auth/types.ts';\n\n/** DCR Registration Request (RFC 7591) */\ninterface DcrRegistrationRequest {\n client_name?: string;\n redirect_uris?: string[];\n grant_types?: string[];\n response_types?: string[];\n token_endpoint_auth_method?: string;\n application_type?: string;\n}\n\n/** DCR Registration Response (RFC 7591) */\ninterface DcrRegistrationResponse {\n client_id: string;\n client_secret?: string;\n client_id_issued_at?: number;\n client_secret_expires_at?: number;\n}\n\n/** Handles Dynamic Client Registration with OAuth servers. */\nexport class DynamicClientRegistrar {\n /**\n * Registers a new OAuth client with the authorization server (RFC 7591).\n *\n * @param registrationEndpoint - Often sourced from remote-controlled AS metadata; pass `options.allowLoopback` explicitly. Defaults to `false`.\n * @param options - Registration options (redirect URI, client name, loopback trust).\n * @returns Client credentials (client ID and secret).\n * @throws Error if registration fails or the server returns an error.\n */\n async registerClient(registrationEndpoint: string, options: DcrRegistrationOptions): Promise<ClientCredentials> {\n const requestBody: DcrRegistrationRequest = {\n client_name: options.clientName || '@mcp-z/client',\n redirect_uris: [options.redirectUri],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'client_secret_basic',\n // The callback listener always binds an OS-assigned loopback port, so the\n // redirect URI is one an authorization server rejects for a web client (SEP-837, RFC 8252).\n application_type: 'native',\n };\n\n // registrationEndpoint is remote-controlled discovery data; allowLoopback\n // must come from the caller's own trust, never from registrationEndpoint.\n const response = await discoveryFetch(\n registrationEndpoint,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: JSON.stringify(requestBody),\n },\n 'registration endpoint',\n { allowLoopback: options.allowLoopback ?? false }\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`DCR registration failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as DcrRegistrationResponse;\n\n if (!data.client_id) {\n throw new Error('DCR registration response missing client_id');\n }\n\n const credentials: ClientCredentials = {\n clientId: data.client_id,\n clientSecret: data.client_secret || '',\n };\n\n if (data.client_id_issued_at) {\n credentials.issuedAt = data.client_id_issued_at;\n }\n\n return credentials;\n }\n}\n"],"names":["discoveryFetch","DynamicClientRegistrar","registerClient","registrationEndpoint","options","requestBody","client_name","clientName","redirect_uris","redirectUri","grant_types","response_types","token_endpoint_auth_method","application_type","response","method","headers","Accept","Connection","body","JSON","stringify","allowLoopback","ok","errorText","text","Error","status","data","json","client_id","credentials","clientId","clientSecret","client_secret","client_id_issued_at","issuedAt"],"mappings":"AAAA;;;CAGC,GAED,SAASA,cAAc,QAAQ,6BAA6B;AAqB5D,4DAA4D,GAC5D,OAAO,MAAMC;IACX;;;;;;;GAOC,GACD,MAAMC,eAAeC,oBAA4B,EAAEC,OAA+B,EAA8B;YA0B3FA;QAzBnB,MAAMC,cAAsC;YAC1CC,aAAaF,QAAQG,UAAU,IAAI;YACnCC,eAAe;gBAACJ,QAAQK,WAAW;aAAC;YACpCC,aAAa;gBAAC;gBAAsB;aAAgB;YACpDC,gBAAgB;gBAAC;aAAO;YACxBC,4BAA4B;YAC5B,0EAA0E;YAC1E,4FAA4F;YAC5FC,kBAAkB;QACpB;QAEA,0EAA0E;QAC1E,0EAA0E;QAC1E,MAAMC,WAAW,MAAMd,eACrBG,sBACA;YACEY,QAAQ;YACRC,SAAS;gBACP,gBAAgB;gBAChBC,QAAQ;gBACRC,YAAY;YACd;YACAC,MAAMC,KAAKC,SAAS,CAAChB;QACvB,GACA,yBACA;YAAEiB,aAAa,GAAElB,yBAAAA,QAAQkB,aAAa,cAArBlB,oCAAAA,yBAAyB;QAAM;QAGlD,IAAI,CAACU,SAASS,EAAE,EAAE;YAChB,MAAMC,YAAY,MAAMV,SAASW,IAAI;YACrC,MAAM,IAAIC,MAAM,CAAC,yBAAyB,EAAEZ,SAASa,MAAM,CAAC,GAAG,EAAEH,WAAW;QAC9E;QAEA,MAAMI,OAAQ,MAAMd,SAASe,IAAI;QAEjC,IAAI,CAACD,KAAKE,SAAS,EAAE;YACnB,MAAM,IAAIJ,MAAM;QAClB;QAEA,MAAMK,cAAiC;YACrCC,UAAUJ,KAAKE,SAAS;YACxBG,cAAcL,KAAKM,aAAa,IAAI;QACtC;QAEA,IAAIN,KAAKO,mBAAmB,EAAE;YAC5BJ,YAAYK,QAAQ,GAAGR,KAAKO,mBAAmB;QACjD;QAEA,OAAOJ;IACT;AACF"}
|
|
@@ -3,8 +3,8 @@ export function normalizeUrl(input) {
|
|
|
3
3
|
const url = new URL(input);
|
|
4
4
|
url.search = '';
|
|
5
5
|
url.hash = '';
|
|
6
|
-
|
|
7
|
-
return url.origin + url.pathname;
|
|
6
|
+
// Strip after joining: assigning an empty pathname puts the '/' straight back.
|
|
7
|
+
return (url.origin + url.pathname).replace(/\/+$/, '');
|
|
8
8
|
} catch {
|
|
9
9
|
return input.replace(/\/+$/, '');
|
|
10
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/lib/url-utils.ts"],"sourcesContent":["export function normalizeUrl(input: string): string {\n try {\n const url = new URL(input);\n url.search = '';\n url.hash = '';\n
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/lib/url-utils.ts"],"sourcesContent":["export function normalizeUrl(input: string): string {\n try {\n const url = new URL(input);\n url.search = '';\n url.hash = '';\n // Strip after joining: assigning an empty pathname puts the '/' straight back.\n return (url.origin + url.pathname).replace(/\\/+$/, '');\n } catch {\n return input.replace(/\\/+$/, '');\n }\n}\n\nexport function joinWellKnown(baseUrl: string, suffix: string): string {\n return `${normalizeUrl(baseUrl)}${suffix}`;\n}\n"],"names":["normalizeUrl","input","url","URL","search","hash","origin","pathname","replace","joinWellKnown","baseUrl","suffix"],"mappings":"AAAA,OAAO,SAASA,aAAaC,KAAa;IACxC,IAAI;QACF,MAAMC,MAAM,IAAIC,IAAIF;QACpBC,IAAIE,MAAM,GAAG;QACbF,IAAIG,IAAI,GAAG;QACX,+EAA+E;QAC/E,OAAO,AAACH,CAAAA,IAAII,MAAM,GAAGJ,IAAIK,QAAQ,AAAD,EAAGC,OAAO,CAAC,QAAQ;IACrD,EAAE,OAAM;QACN,OAAOP,MAAMO,OAAO,CAAC,QAAQ;IAC/B;AACF;AAEA,OAAO,SAASC,cAAcC,OAAe,EAAEC,MAAc;IAC3D,OAAO,GAAGX,aAAaU,WAAWC,QAAQ;AAC5C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-z/client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Programmatic MCP client library for Node.js - connect, discover, and call tools on Model Context Protocol servers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"format": "tsds format",
|
|
54
54
|
"generate:types": "json2ts schemas/servers.schema.json schemas/servers.d.ts --bannerComment \"/* eslint-disable */\n/* Auto-generated from schemas/servers.schema.json - DO NOT EDIT */\"",
|
|
55
55
|
"prepublish:check": "ncp",
|
|
56
|
-
"prepublishOnly": "tsds validate",
|
|
56
|
+
"prepublishOnly": "node scripts/require-dist-tag.cjs && tsds validate",
|
|
57
57
|
"test": "tsds test:node --no-timeouts",
|
|
58
58
|
"test:engines": "nvu engines tsds test:node --no-timeouts",
|
|
59
59
|
"version": "tsds version"
|