fastmcp 4.23.11 → 4.23.13

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 (34) hide show
  1. package/dist/FastMCP.cjs +3 -3
  2. package/dist/FastMCP.d.cts +2 -2
  3. package/dist/FastMCP.d.ts +2 -2
  4. package/dist/FastMCP.js +2 -2
  5. package/dist/{OAuthProvider-BcDb5bOz.d.cts → OAuthProvider-84BWWDBF.d.cts} +55 -1
  6. package/dist/{OAuthProvider-BcDb5bOz.d.ts → OAuthProvider-84BWWDBF.d.ts} +55 -1
  7. package/dist/auth/index.cjs +4 -2
  8. package/dist/auth/index.cjs.map +1 -1
  9. package/dist/auth/index.d.cts +10 -2
  10. package/dist/auth/index.d.ts +10 -2
  11. package/dist/auth/index.js +3 -1
  12. package/dist/bin/fastmcp.cjs +22 -2
  13. package/dist/bin/fastmcp.cjs.map +1 -1
  14. package/dist/bin/fastmcp.js +22 -2
  15. package/dist/bin/fastmcp.js.map +1 -1
  16. package/dist/{chunk-X5ITW7FV.cjs → chunk-4P2GDM7W.cjs} +74 -35
  17. package/dist/chunk-4P2GDM7W.cjs.map +1 -0
  18. package/dist/{chunk-4OWHPJEL.cjs → chunk-BV4PWTT6.cjs} +214 -43
  19. package/dist/chunk-BV4PWTT6.cjs.map +1 -0
  20. package/dist/{chunk-G44SPVP7.js → chunk-CAQLTSJ5.js} +211 -40
  21. package/dist/chunk-CAQLTSJ5.js.map +1 -0
  22. package/dist/{chunk-XUUJOVC7.js → chunk-ULNQF5KG.js} +57 -18
  23. package/dist/chunk-ULNQF5KG.js.map +1 -0
  24. package/dist/examples/custom-routes.cjs +3 -3
  25. package/dist/examples/custom-routes.js +2 -2
  26. package/dist/openapi/index.cjs +7 -7
  27. package/dist/openapi/index.d.cts +1 -1
  28. package/dist/openapi/index.d.ts +1 -1
  29. package/dist/openapi/index.js +2 -2
  30. package/package.json +2 -1
  31. package/dist/chunk-4OWHPJEL.cjs.map +0 -1
  32. package/dist/chunk-G44SPVP7.js.map +0 -1
  33. package/dist/chunk-X5ITW7FV.cjs.map +0 -1
  34. package/dist/chunk-XUUJOVC7.js.map +0 -1
package/dist/FastMCP.cjs CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
 
14
- var _chunkX5ITW7FVcjs = require('./chunk-X5ITW7FV.cjs');
14
+ var _chunk4P2GDM7Wcjs = require('./chunk-4P2GDM7W.cjs');
15
15
 
16
16
 
17
17
 
@@ -24,7 +24,7 @@ var _chunkX5ITW7FVcjs = require('./chunk-X5ITW7FV.cjs');
24
24
 
25
25
 
26
26
 
27
- var _chunk4OWHPJELcjs = require('./chunk-4OWHPJEL.cjs');
27
+ var _chunkBV4PWTT6cjs = require('./chunk-BV4PWTT6.cjs');
28
28
  require('./chunk-6TDYSPLC.cjs');
29
29
 
30
30
 
@@ -50,5 +50,5 @@ require('./chunk-6TDYSPLC.cjs');
50
50
 
51
51
 
52
52
 
53
- exports.AuthProvider = _chunk4OWHPJELcjs.AuthProvider; exports.AzureProvider = _chunk4OWHPJELcjs.AzureProvider; exports.DiscoveryDocumentCache = _chunkX5ITW7FVcjs.DiscoveryDocumentCache; exports.FastMCP = _chunkX5ITW7FVcjs.FastMCP; exports.FastMCPError = _chunkX5ITW7FVcjs.FastMCPError; exports.FastMCPSession = _chunkX5ITW7FVcjs.FastMCPSession; exports.GitHubProvider = _chunk4OWHPJELcjs.GitHubProvider; exports.GoogleProvider = _chunk4OWHPJELcjs.GoogleProvider; exports.MEDIA_FETCH_TIMEOUT_MS = _chunkX5ITW7FVcjs.MEDIA_FETCH_TIMEOUT_MS; exports.OAuthProvider = _chunk4OWHPJELcjs.OAuthProvider; exports.ServerState = _chunkX5ITW7FVcjs.ServerState; exports.SessionError = _chunkX5ITW7FVcjs.SessionError; exports.UnexpectedStateError = _chunkX5ITW7FVcjs.UnexpectedStateError; exports.UserError = _chunkX5ITW7FVcjs.UserError; exports.audioContent = _chunkX5ITW7FVcjs.audioContent; exports.getAuthSession = _chunk4OWHPJELcjs.getAuthSession; exports.imageContent = _chunkX5ITW7FVcjs.imageContent; exports.jsonSchemaAdapter = _chunkX5ITW7FVcjs.jsonSchemaAdapter; exports.requireAll = _chunk4OWHPJELcjs.requireAll; exports.requireAny = _chunk4OWHPJELcjs.requireAny; exports.requireAuth = _chunk4OWHPJELcjs.requireAuth; exports.requireRole = _chunk4OWHPJELcjs.requireRole; exports.requireScopes = _chunk4OWHPJELcjs.requireScopes;
53
+ exports.AuthProvider = _chunkBV4PWTT6cjs.AuthProvider; exports.AzureProvider = _chunkBV4PWTT6cjs.AzureProvider; exports.DiscoveryDocumentCache = _chunk4P2GDM7Wcjs.DiscoveryDocumentCache; exports.FastMCP = _chunk4P2GDM7Wcjs.FastMCP; exports.FastMCPError = _chunk4P2GDM7Wcjs.FastMCPError; exports.FastMCPSession = _chunk4P2GDM7Wcjs.FastMCPSession; exports.GitHubProvider = _chunkBV4PWTT6cjs.GitHubProvider; exports.GoogleProvider = _chunkBV4PWTT6cjs.GoogleProvider; exports.MEDIA_FETCH_TIMEOUT_MS = _chunk4P2GDM7Wcjs.MEDIA_FETCH_TIMEOUT_MS; exports.OAuthProvider = _chunkBV4PWTT6cjs.OAuthProvider; exports.ServerState = _chunk4P2GDM7Wcjs.ServerState; exports.SessionError = _chunk4P2GDM7Wcjs.SessionError; exports.UnexpectedStateError = _chunk4P2GDM7Wcjs.UnexpectedStateError; exports.UserError = _chunk4P2GDM7Wcjs.UserError; exports.audioContent = _chunk4P2GDM7Wcjs.audioContent; exports.getAuthSession = _chunkBV4PWTT6cjs.getAuthSession; exports.imageContent = _chunk4P2GDM7Wcjs.imageContent; exports.jsonSchemaAdapter = _chunk4P2GDM7Wcjs.jsonSchemaAdapter; exports.requireAll = _chunkBV4PWTT6cjs.requireAll; exports.requireAny = _chunkBV4PWTT6cjs.requireAny; exports.requireAuth = _chunkBV4PWTT6cjs.requireAuth; exports.requireRole = _chunkBV4PWTT6cjs.requireRole; exports.requireScopes = _chunkBV4PWTT6cjs.requireScopes;
54
54
  //# sourceMappingURL=FastMCP.cjs.map
@@ -12,8 +12,8 @@ import { CorsOptions } from 'mcp-proxy';
12
12
  export { CorsOptions } from 'mcp-proxy';
13
13
  import { StrictEventEmitter } from 'strict-event-emitter-types';
14
14
  import { z } from 'zod';
15
- import { A as AuthProvider, O as OAuthSession, a as OAuthProxy } from './OAuthProvider-BcDb5bOz.cjs';
16
- export { j as AuthProviderConfig, b as AzureProvider, k as AzureProviderConfig, l as AzureSession, m as GenericOAuthProviderConfig, G as GitHubProvider, n as GitHubSession, c as GoogleProvider, o as GoogleSession, d as OAuthProvider, g as getAuthSession, r as requireAll, e as requireAny, f as requireAuth, h as requireRole, i as requireScopes } from './OAuthProvider-BcDb5bOz.cjs';
15
+ import { A as AuthProvider, O as OAuthSession, a as OAuthProxy } from './OAuthProvider-84BWWDBF.cjs';
16
+ export { j as AuthProviderConfig, b as AzureProvider, k as AzureProviderConfig, l as AzureSession, m as GenericOAuthProviderConfig, G as GitHubProvider, n as GitHubSession, c as GoogleProvider, o as GoogleSession, d as OAuthProvider, g as getAuthSession, r as requireAll, e as requireAny, f as requireAuth, h as requireRole, i as requireScopes } from './OAuthProvider-84BWWDBF.cjs';
17
17
  import 'node:http';
18
18
 
19
19
  declare class DiscoveryDocumentCache {
package/dist/FastMCP.d.ts CHANGED
@@ -12,8 +12,8 @@ import { CorsOptions } from 'mcp-proxy';
12
12
  export { CorsOptions } from 'mcp-proxy';
13
13
  import { StrictEventEmitter } from 'strict-event-emitter-types';
14
14
  import { z } from 'zod';
15
- import { A as AuthProvider, O as OAuthSession, a as OAuthProxy } from './OAuthProvider-BcDb5bOz.js';
16
- export { j as AuthProviderConfig, b as AzureProvider, k as AzureProviderConfig, l as AzureSession, m as GenericOAuthProviderConfig, G as GitHubProvider, n as GitHubSession, c as GoogleProvider, o as GoogleSession, d as OAuthProvider, g as getAuthSession, r as requireAll, e as requireAny, f as requireAuth, h as requireRole, i as requireScopes } from './OAuthProvider-BcDb5bOz.js';
15
+ import { A as AuthProvider, O as OAuthSession, a as OAuthProxy } from './OAuthProvider-84BWWDBF.js';
16
+ export { j as AuthProviderConfig, b as AzureProvider, k as AzureProviderConfig, l as AzureSession, m as GenericOAuthProviderConfig, G as GitHubProvider, n as GitHubSession, c as GoogleProvider, o as GoogleSession, d as OAuthProvider, g as getAuthSession, r as requireAll, e as requireAny, f as requireAuth, h as requireRole, i as requireScopes } from './OAuthProvider-84BWWDBF.js';
17
17
  import 'node:http';
18
18
 
19
19
  declare class DiscoveryDocumentCache {
package/dist/FastMCP.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  audioContent,
12
12
  imageContent,
13
13
  jsonSchemaAdapter
14
- } from "./chunk-XUUJOVC7.js";
14
+ } from "./chunk-ULNQF5KG.js";
15
15
  import {
16
16
  AuthProvider,
17
17
  AzureProvider,
@@ -24,7 +24,7 @@ import {
24
24
  requireAuth,
25
25
  requireRole,
26
26
  requireScopes
27
- } from "./chunk-G44SPVP7.js";
27
+ } from "./chunk-CAQLTSJ5.js";
28
28
  import "./chunk-OKKFL2M4.js";
29
29
  export {
30
30
  AuthProvider,
@@ -10,6 +10,11 @@ import { IncomingMessage } from 'node:http';
10
10
  declare const DEFAULT_ACCESS_TOKEN_TTL = 3600;
11
11
  declare const DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = 31536000;
12
12
  declare const DEFAULT_REFRESH_TOKEN_TTL = 2592000;
13
+ /**
14
+ * The token endpoint authentication methods the proxy can verify, and so the
15
+ * only ones a client may register with.
16
+ */
17
+ declare const TOKEN_ENDPOINT_AUTH_METHODS: readonly ["client_secret_basic", "client_secret_post", "none"];
13
18
  declare const DEFAULT_AUTHORIZATION_CODE_TTL = 300;
14
19
  declare const DEFAULT_TRANSACTION_TTL = 600;
15
20
  /**
@@ -368,6 +373,17 @@ interface ProxyDCRClient {
368
373
  * Absent for a Dynamic Client Registration.
369
374
  */
370
375
  source?: "cimd";
376
+ /**
377
+ * How the client authenticates at the token endpoint, as it registered.
378
+ * `"none"` is a public client: it is issued no secret and must use S256
379
+ * PKCE. Either secret method obliges it to authenticate on every token
380
+ * request.
381
+ *
382
+ * Absent on registrations written before this was recorded, when every
383
+ * client was issued a secret whatever it asked for. Those are treated as
384
+ * public clients that may still authenticate with that secret.
385
+ */
386
+ tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
371
387
  }
372
388
  /**
373
389
  * OAuth refresh token request
@@ -379,11 +395,23 @@ interface RefreshRequest {
379
395
  refresh_token: string;
380
396
  scope?: string;
381
397
  }
398
+ /**
399
+ * Token endpoint authentication methods a Dynamic Client Registration may ask
400
+ * for (RFC 7591 §2).
401
+ */
402
+ type TokenEndpointAuthMethod = (typeof TOKEN_ENDPOINT_AUTH_METHODS)[number];
382
403
  /**
383
404
  * Token mapping for JWT swap pattern
384
405
  * Maps JTI to upstream token reference
385
406
  */
386
407
  interface TokenMapping {
408
+ /**
409
+ * Whether the client authenticated with its secret when this token was
410
+ * issued. A refresh token issued that way is only redeemed by a client that
411
+ * authenticates again; otherwise omitting the secret would turn it back into
412
+ * a bearer token.
413
+ */
414
+ clientAuthenticated?: boolean;
387
415
  /** Client ID */
388
416
  clientId: string;
389
417
  /** Creation timestamp */
@@ -581,6 +609,20 @@ declare class OAuthProxy {
581
609
  * RFC 7591 Dynamic Client Registration
582
610
  */
583
611
  registerClient(request: DCRRequest): Promise<DCRResponse>;
612
+ /**
613
+ * RFC 6749 §2.3 / §3.2.1: identify the client behind a token request and,
614
+ * when it presents a secret, authenticate it.
615
+ *
616
+ * A client registered with a secret method must present its secret. A
617
+ * public client must not present one: it was issued none. A registration
618
+ * that predates recorded auth methods may do either — it was issued a
619
+ * secret whatever it asked for — and is treated as unauthenticated when it
620
+ * leaves it out, which the grants then hold it to.
621
+ *
622
+ * Throws `invalid_client` on any failure. Nothing here touches grant state,
623
+ * so a caller that fails cannot spend a code or refresh token.
624
+ */
625
+ private authenticateClient;
584
626
  /**
585
627
  * Calculate access token TTL from upstream tokens
586
628
  */
@@ -669,6 +711,12 @@ declare class OAuthProxy {
669
711
  * GitHub Apps return URL-encoded format, most providers return JSON
670
712
  */
671
713
  private parseTokenResponse;
714
+ /**
715
+ * Storage key for the client binding of a passthrough-mode upstream refresh
716
+ * token. Keyed by its hash: storage keys are not encrypted, and the token
717
+ * itself is a live upstream credential.
718
+ */
719
+ private passthroughRefreshKey;
672
720
  /**
673
721
  * Redirect to upstream OAuth provider
674
722
  */
@@ -698,6 +746,12 @@ declare class OAuthProxy {
698
746
  * `expiresAt`.
699
747
  */
700
748
  private restoreMapping;
749
+ /**
750
+ * Record which client a passthrough-mode upstream refresh token was handed
751
+ * to. It lives as long as the upstream says the token does, or as the
752
+ * configured refresh-token lifetime when it does not say.
753
+ */
754
+ private savePassthroughRefreshBinding;
701
755
  /**
702
756
  * Start periodic cleanup of expired transactions and codes
703
757
  */
@@ -990,4 +1044,4 @@ declare class OAuthProvider<TSession extends OAuthSession = OAuthSession> extend
990
1044
  protected getTokenEndpoint(): string;
991
1045
  }
992
1046
 
993
- export { AuthProvider as A, type DCRClientMetadata as B, type ConsentData as C, DEFAULT_ACCESS_TOKEN_TTL as D, type DCRRequest as E, type DCRResponse as F, GitHubProvider as G, type OAuthError as H, type OAuthProviderConfig as I, type OAuthProxyConfig as J, type ProxyDCRClient as K, type TokenMapping as L, type TokenRequest as M, type TokenResponse as N, type OAuthSession as O, type PKCEPair as P, type RefreshRequest as R, type TokenStorage as T, type UpstreamTokenSet as U, OAuthProxy as a, AzureProvider as b, GoogleProvider as c, OAuthProvider as d, requireAny as e, requireAuth as f, getAuthSession as g, requireRole as h, requireScopes as i, type AuthProviderConfig as j, type AzureProviderConfig as k, type AzureSession as l, type GenericOAuthProviderConfig as m, type GitHubSession as n, type GoogleSession as o, type OAuthTransaction as p, type TokenVerifier as q, requireAll as r, type TokenVerificationResult as s, OAuthProxyError as t, DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH as u, DEFAULT_AUTHORIZATION_CODE_TTL as v, DEFAULT_REFRESH_TOKEN_TTL as w, DEFAULT_TRANSACTION_TTL as x, type AuthorizationParams as y, type ClientCode as z };
1047
+ export { AuthProvider as A, type ClientCode as B, type ConsentData as C, DEFAULT_ACCESS_TOKEN_TTL as D, type DCRClientMetadata as E, type DCRRequest as F, GitHubProvider as G, type DCRResponse as H, type OAuthError as I, type OAuthProviderConfig as J, type OAuthProxyConfig as K, type ProxyDCRClient as L, type TokenEndpointAuthMethod as M, type TokenMapping as N, type OAuthSession as O, type PKCEPair as P, type TokenRequest as Q, type RefreshRequest as R, type TokenResponse as S, type TokenStorage as T, type UpstreamTokenSet as U, OAuthProxy as a, AzureProvider as b, GoogleProvider as c, OAuthProvider as d, requireAny as e, requireAuth as f, getAuthSession as g, requireRole as h, requireScopes as i, type AuthProviderConfig as j, type AzureProviderConfig as k, type AzureSession as l, type GenericOAuthProviderConfig as m, type GitHubSession as n, type GoogleSession as o, type OAuthTransaction as p, type TokenVerifier as q, requireAll as r, type TokenVerificationResult as s, OAuthProxyError as t, DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH as u, DEFAULT_AUTHORIZATION_CODE_TTL as v, DEFAULT_REFRESH_TOKEN_TTL as w, DEFAULT_TRANSACTION_TTL as x, TOKEN_ENDPOINT_AUTH_METHODS as y, type AuthorizationParams as z };
@@ -10,6 +10,11 @@ import { IncomingMessage } from 'node:http';
10
10
  declare const DEFAULT_ACCESS_TOKEN_TTL = 3600;
11
11
  declare const DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = 31536000;
12
12
  declare const DEFAULT_REFRESH_TOKEN_TTL = 2592000;
13
+ /**
14
+ * The token endpoint authentication methods the proxy can verify, and so the
15
+ * only ones a client may register with.
16
+ */
17
+ declare const TOKEN_ENDPOINT_AUTH_METHODS: readonly ["client_secret_basic", "client_secret_post", "none"];
13
18
  declare const DEFAULT_AUTHORIZATION_CODE_TTL = 300;
14
19
  declare const DEFAULT_TRANSACTION_TTL = 600;
15
20
  /**
@@ -368,6 +373,17 @@ interface ProxyDCRClient {
368
373
  * Absent for a Dynamic Client Registration.
369
374
  */
370
375
  source?: "cimd";
376
+ /**
377
+ * How the client authenticates at the token endpoint, as it registered.
378
+ * `"none"` is a public client: it is issued no secret and must use S256
379
+ * PKCE. Either secret method obliges it to authenticate on every token
380
+ * request.
381
+ *
382
+ * Absent on registrations written before this was recorded, when every
383
+ * client was issued a secret whatever it asked for. Those are treated as
384
+ * public clients that may still authenticate with that secret.
385
+ */
386
+ tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
371
387
  }
372
388
  /**
373
389
  * OAuth refresh token request
@@ -379,11 +395,23 @@ interface RefreshRequest {
379
395
  refresh_token: string;
380
396
  scope?: string;
381
397
  }
398
+ /**
399
+ * Token endpoint authentication methods a Dynamic Client Registration may ask
400
+ * for (RFC 7591 §2).
401
+ */
402
+ type TokenEndpointAuthMethod = (typeof TOKEN_ENDPOINT_AUTH_METHODS)[number];
382
403
  /**
383
404
  * Token mapping for JWT swap pattern
384
405
  * Maps JTI to upstream token reference
385
406
  */
386
407
  interface TokenMapping {
408
+ /**
409
+ * Whether the client authenticated with its secret when this token was
410
+ * issued. A refresh token issued that way is only redeemed by a client that
411
+ * authenticates again; otherwise omitting the secret would turn it back into
412
+ * a bearer token.
413
+ */
414
+ clientAuthenticated?: boolean;
387
415
  /** Client ID */
388
416
  clientId: string;
389
417
  /** Creation timestamp */
@@ -581,6 +609,20 @@ declare class OAuthProxy {
581
609
  * RFC 7591 Dynamic Client Registration
582
610
  */
583
611
  registerClient(request: DCRRequest): Promise<DCRResponse>;
612
+ /**
613
+ * RFC 6749 §2.3 / §3.2.1: identify the client behind a token request and,
614
+ * when it presents a secret, authenticate it.
615
+ *
616
+ * A client registered with a secret method must present its secret. A
617
+ * public client must not present one: it was issued none. A registration
618
+ * that predates recorded auth methods may do either — it was issued a
619
+ * secret whatever it asked for — and is treated as unauthenticated when it
620
+ * leaves it out, which the grants then hold it to.
621
+ *
622
+ * Throws `invalid_client` on any failure. Nothing here touches grant state,
623
+ * so a caller that fails cannot spend a code or refresh token.
624
+ */
625
+ private authenticateClient;
584
626
  /**
585
627
  * Calculate access token TTL from upstream tokens
586
628
  */
@@ -669,6 +711,12 @@ declare class OAuthProxy {
669
711
  * GitHub Apps return URL-encoded format, most providers return JSON
670
712
  */
671
713
  private parseTokenResponse;
714
+ /**
715
+ * Storage key for the client binding of a passthrough-mode upstream refresh
716
+ * token. Keyed by its hash: storage keys are not encrypted, and the token
717
+ * itself is a live upstream credential.
718
+ */
719
+ private passthroughRefreshKey;
672
720
  /**
673
721
  * Redirect to upstream OAuth provider
674
722
  */
@@ -698,6 +746,12 @@ declare class OAuthProxy {
698
746
  * `expiresAt`.
699
747
  */
700
748
  private restoreMapping;
749
+ /**
750
+ * Record which client a passthrough-mode upstream refresh token was handed
751
+ * to. It lives as long as the upstream says the token does, or as the
752
+ * configured refresh-token lifetime when it does not say.
753
+ */
754
+ private savePassthroughRefreshBinding;
701
755
  /**
702
756
  * Start periodic cleanup of expired transactions and codes
703
757
  */
@@ -990,4 +1044,4 @@ declare class OAuthProvider<TSession extends OAuthSession = OAuthSession> extend
990
1044
  protected getTokenEndpoint(): string;
991
1045
  }
992
1046
 
993
- export { AuthProvider as A, type DCRClientMetadata as B, type ConsentData as C, DEFAULT_ACCESS_TOKEN_TTL as D, type DCRRequest as E, type DCRResponse as F, GitHubProvider as G, type OAuthError as H, type OAuthProviderConfig as I, type OAuthProxyConfig as J, type ProxyDCRClient as K, type TokenMapping as L, type TokenRequest as M, type TokenResponse as N, type OAuthSession as O, type PKCEPair as P, type RefreshRequest as R, type TokenStorage as T, type UpstreamTokenSet as U, OAuthProxy as a, AzureProvider as b, GoogleProvider as c, OAuthProvider as d, requireAny as e, requireAuth as f, getAuthSession as g, requireRole as h, requireScopes as i, type AuthProviderConfig as j, type AzureProviderConfig as k, type AzureSession as l, type GenericOAuthProviderConfig as m, type GitHubSession as n, type GoogleSession as o, type OAuthTransaction as p, type TokenVerifier as q, requireAll as r, type TokenVerificationResult as s, OAuthProxyError as t, DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH as u, DEFAULT_AUTHORIZATION_CODE_TTL as v, DEFAULT_REFRESH_TOKEN_TTL as w, DEFAULT_TRANSACTION_TTL as x, type AuthorizationParams as y, type ClientCode as z };
1047
+ export { AuthProvider as A, type ClientCode as B, type ConsentData as C, DEFAULT_ACCESS_TOKEN_TTL as D, type DCRClientMetadata as E, type DCRRequest as F, GitHubProvider as G, type DCRResponse as H, type OAuthError as I, type OAuthProviderConfig as J, type OAuthProxyConfig as K, type ProxyDCRClient as L, type TokenEndpointAuthMethod as M, type TokenMapping as N, type OAuthSession as O, type PKCEPair as P, type TokenRequest as Q, type RefreshRequest as R, type TokenResponse as S, type TokenStorage as T, type UpstreamTokenSet as U, OAuthProxy as a, AzureProvider as b, GoogleProvider as c, OAuthProvider as d, requireAny as e, requireAuth as f, getAuthSession as g, requireRole as h, requireScopes as i, type AuthProviderConfig as j, type AzureProviderConfig as k, type AzureSession as l, type GenericOAuthProviderConfig as m, type GitHubSession as n, type GoogleSession as o, type OAuthTransaction as p, type TokenVerifier as q, requireAll as r, type TokenVerificationResult as s, OAuthProxyError as t, DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH as u, DEFAULT_AUTHORIZATION_CODE_TTL as v, DEFAULT_REFRESH_TOKEN_TTL as w, DEFAULT_TRANSACTION_TTL as x, TOKEN_ENDPOINT_AUTH_METHODS as y, type AuthorizationParams as z };
@@ -24,8 +24,8 @@
24
24
 
25
25
 
26
26
 
27
- var _chunk4OWHPJELcjs = require('../chunk-4OWHPJEL.cjs');
28
27
 
28
+ var _chunkBV4PWTT6cjs = require('../chunk-BV4PWTT6.cjs');
29
29
 
30
30
 
31
31
 
@@ -51,5 +51,7 @@ var _chunk4OWHPJELcjs = require('../chunk-4OWHPJEL.cjs');
51
51
 
52
52
 
53
53
 
54
- exports.AuthProvider = _chunk4OWHPJELcjs.AuthProvider; exports.AzureProvider = _chunk4OWHPJELcjs.AzureProvider; exports.ConsentManager = _chunk4OWHPJELcjs.ConsentManager; exports.DEFAULT_ACCESS_TOKEN_TTL = _chunk4OWHPJELcjs.DEFAULT_ACCESS_TOKEN_TTL; exports.DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = _chunk4OWHPJELcjs.DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH; exports.DEFAULT_AUTHORIZATION_CODE_TTL = _chunk4OWHPJELcjs.DEFAULT_AUTHORIZATION_CODE_TTL; exports.DEFAULT_REFRESH_TOKEN_TTL = _chunk4OWHPJELcjs.DEFAULT_REFRESH_TOKEN_TTL; exports.DEFAULT_TRANSACTION_TTL = _chunk4OWHPJELcjs.DEFAULT_TRANSACTION_TTL; exports.DiskStore = _chunk4OWHPJELcjs.DiskStore; exports.EncryptedTokenStorage = _chunk4OWHPJELcjs.EncryptedTokenStorage; exports.GitHubProvider = _chunk4OWHPJELcjs.GitHubProvider; exports.GoogleProvider = _chunk4OWHPJELcjs.GoogleProvider; exports.JWKSVerifier = _chunk4OWHPJELcjs.JWKSVerifier; exports.JWTIssuer = _chunk4OWHPJELcjs.JWTIssuer; exports.MemoryTokenStorage = _chunk4OWHPJELcjs.MemoryTokenStorage; exports.OAuthProvider = _chunk4OWHPJELcjs.OAuthProvider; exports.OAuthProxy = _chunk4OWHPJELcjs.OAuthProxy; exports.OAuthProxyError = _chunk4OWHPJELcjs.OAuthProxyError; exports.PKCEUtils = _chunk4OWHPJELcjs.PKCEUtils; exports.getAuthSession = _chunk4OWHPJELcjs.getAuthSession; exports.requireAll = _chunk4OWHPJELcjs.requireAll; exports.requireAny = _chunk4OWHPJELcjs.requireAny; exports.requireAuth = _chunk4OWHPJELcjs.requireAuth; exports.requireRole = _chunk4OWHPJELcjs.requireRole; exports.requireScopes = _chunk4OWHPJELcjs.requireScopes;
54
+
55
+
56
+ exports.AuthProvider = _chunkBV4PWTT6cjs.AuthProvider; exports.AzureProvider = _chunkBV4PWTT6cjs.AzureProvider; exports.ConsentManager = _chunkBV4PWTT6cjs.ConsentManager; exports.DEFAULT_ACCESS_TOKEN_TTL = _chunkBV4PWTT6cjs.DEFAULT_ACCESS_TOKEN_TTL; exports.DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = _chunkBV4PWTT6cjs.DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH; exports.DEFAULT_AUTHORIZATION_CODE_TTL = _chunkBV4PWTT6cjs.DEFAULT_AUTHORIZATION_CODE_TTL; exports.DEFAULT_REFRESH_TOKEN_TTL = _chunkBV4PWTT6cjs.DEFAULT_REFRESH_TOKEN_TTL; exports.DEFAULT_TRANSACTION_TTL = _chunkBV4PWTT6cjs.DEFAULT_TRANSACTION_TTL; exports.DiskStore = _chunkBV4PWTT6cjs.DiskStore; exports.EncryptedTokenStorage = _chunkBV4PWTT6cjs.EncryptedTokenStorage; exports.GitHubProvider = _chunkBV4PWTT6cjs.GitHubProvider; exports.GoogleProvider = _chunkBV4PWTT6cjs.GoogleProvider; exports.JWKSVerifier = _chunkBV4PWTT6cjs.JWKSVerifier; exports.JWTIssuer = _chunkBV4PWTT6cjs.JWTIssuer; exports.MemoryTokenStorage = _chunkBV4PWTT6cjs.MemoryTokenStorage; exports.OAuthProvider = _chunkBV4PWTT6cjs.OAuthProvider; exports.OAuthProxy = _chunkBV4PWTT6cjs.OAuthProxy; exports.OAuthProxyError = _chunkBV4PWTT6cjs.OAuthProxyError; exports.PKCEUtils = _chunkBV4PWTT6cjs.PKCEUtils; exports.TOKEN_ENDPOINT_AUTH_METHODS = _chunkBV4PWTT6cjs.TOKEN_ENDPOINT_AUTH_METHODS; exports.getAuthSession = _chunkBV4PWTT6cjs.getAuthSession; exports.requireAll = _chunkBV4PWTT6cjs.requireAll; exports.requireAny = _chunkBV4PWTT6cjs.requireAny; exports.requireAuth = _chunkBV4PWTT6cjs.requireAuth; exports.requireRole = _chunkBV4PWTT6cjs.requireRole; exports.requireScopes = _chunkBV4PWTT6cjs.requireScopes;
55
57
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/fastmcp/fastmcp/dist/auth/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,ghDAAC","file":"/home/runner/work/fastmcp/fastmcp/dist/auth/index.cjs"}
1
+ {"version":3,"sources":["/home/runner/work/fastmcp/fastmcp/dist/auth/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,qmDAAC","file":"/home/runner/work/fastmcp/fastmcp/dist/auth/index.cjs"}
@@ -1,5 +1,5 @@
1
- import { p as OAuthTransaction, C as ConsentData, T as TokenStorage, q as TokenVerifier, s as TokenVerificationResult, P as PKCEPair } from '../OAuthProvider-BcDb5bOz.cjs';
2
- export { A as AuthProvider, j as AuthProviderConfig, y as AuthorizationParams, b as AzureProvider, k as AzureProviderConfig, l as AzureSession, z as ClientCode, B as DCRClientMetadata, E as DCRRequest, F as DCRResponse, D as DEFAULT_ACCESS_TOKEN_TTL, u as DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH, v as DEFAULT_AUTHORIZATION_CODE_TTL, w as DEFAULT_REFRESH_TOKEN_TTL, x as DEFAULT_TRANSACTION_TTL, m as GenericOAuthProviderConfig, G as GitHubProvider, n as GitHubSession, c as GoogleProvider, o as GoogleSession, H as OAuthError, d as OAuthProvider, I as OAuthProviderConfig, a as OAuthProxy, J as OAuthProxyConfig, t as OAuthProxyError, O as OAuthSession, K as ProxyDCRClient, R as RefreshRequest, L as TokenMapping, M as TokenRequest, N as TokenResponse, U as UpstreamTokenSet, g as getAuthSession, r as requireAll, e as requireAny, f as requireAuth, h as requireRole, i as requireScopes } from '../OAuthProvider-BcDb5bOz.cjs';
1
+ import { p as OAuthTransaction, C as ConsentData, T as TokenStorage, q as TokenVerifier, s as TokenVerificationResult, P as PKCEPair } from '../OAuthProvider-84BWWDBF.cjs';
2
+ export { A as AuthProvider, j as AuthProviderConfig, z as AuthorizationParams, b as AzureProvider, k as AzureProviderConfig, l as AzureSession, B as ClientCode, E as DCRClientMetadata, F as DCRRequest, H as DCRResponse, D as DEFAULT_ACCESS_TOKEN_TTL, u as DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH, v as DEFAULT_AUTHORIZATION_CODE_TTL, w as DEFAULT_REFRESH_TOKEN_TTL, x as DEFAULT_TRANSACTION_TTL, m as GenericOAuthProviderConfig, G as GitHubProvider, n as GitHubSession, c as GoogleProvider, o as GoogleSession, I as OAuthError, d as OAuthProvider, J as OAuthProviderConfig, a as OAuthProxy, K as OAuthProxyConfig, t as OAuthProxyError, O as OAuthSession, L as ProxyDCRClient, R as RefreshRequest, y as TOKEN_ENDPOINT_AUTH_METHODS, M as TokenEndpointAuthMethod, N as TokenMapping, Q as TokenRequest, S as TokenResponse, U as UpstreamTokenSet, g as getAuthSession, r as requireAll, e as requireAny, f as requireAuth, h as requireRole, i as requireScopes } from '../OAuthProvider-84BWWDBF.cjs';
3
3
  import 'node:http';
4
4
 
5
5
  /**
@@ -150,6 +150,14 @@ interface JWTClaims {
150
150
  jti: string;
151
151
  /** Scopes */
152
152
  scope: string[];
153
+ /**
154
+ * Which kind of token this is. Access and refresh tokens share a signing
155
+ * key, audience and claim set, so without this either one verifies as the
156
+ * other: an access token — sent on every request — could be redeemed for a
157
+ * refresh token, and a refresh token presented as a bearer credential.
158
+ * Absent from tokens issued before it existed; those are refused as both.
159
+ */
160
+ token_use?: "access" | "refresh";
153
161
  }
154
162
  /**
155
163
  * JWT Issuer configuration
@@ -1,5 +1,5 @@
1
- import { p as OAuthTransaction, C as ConsentData, T as TokenStorage, q as TokenVerifier, s as TokenVerificationResult, P as PKCEPair } from '../OAuthProvider-BcDb5bOz.js';
2
- export { A as AuthProvider, j as AuthProviderConfig, y as AuthorizationParams, b as AzureProvider, k as AzureProviderConfig, l as AzureSession, z as ClientCode, B as DCRClientMetadata, E as DCRRequest, F as DCRResponse, D as DEFAULT_ACCESS_TOKEN_TTL, u as DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH, v as DEFAULT_AUTHORIZATION_CODE_TTL, w as DEFAULT_REFRESH_TOKEN_TTL, x as DEFAULT_TRANSACTION_TTL, m as GenericOAuthProviderConfig, G as GitHubProvider, n as GitHubSession, c as GoogleProvider, o as GoogleSession, H as OAuthError, d as OAuthProvider, I as OAuthProviderConfig, a as OAuthProxy, J as OAuthProxyConfig, t as OAuthProxyError, O as OAuthSession, K as ProxyDCRClient, R as RefreshRequest, L as TokenMapping, M as TokenRequest, N as TokenResponse, U as UpstreamTokenSet, g as getAuthSession, r as requireAll, e as requireAny, f as requireAuth, h as requireRole, i as requireScopes } from '../OAuthProvider-BcDb5bOz.js';
1
+ import { p as OAuthTransaction, C as ConsentData, T as TokenStorage, q as TokenVerifier, s as TokenVerificationResult, P as PKCEPair } from '../OAuthProvider-84BWWDBF.js';
2
+ export { A as AuthProvider, j as AuthProviderConfig, z as AuthorizationParams, b as AzureProvider, k as AzureProviderConfig, l as AzureSession, B as ClientCode, E as DCRClientMetadata, F as DCRRequest, H as DCRResponse, D as DEFAULT_ACCESS_TOKEN_TTL, u as DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH, v as DEFAULT_AUTHORIZATION_CODE_TTL, w as DEFAULT_REFRESH_TOKEN_TTL, x as DEFAULT_TRANSACTION_TTL, m as GenericOAuthProviderConfig, G as GitHubProvider, n as GitHubSession, c as GoogleProvider, o as GoogleSession, I as OAuthError, d as OAuthProvider, J as OAuthProviderConfig, a as OAuthProxy, K as OAuthProxyConfig, t as OAuthProxyError, O as OAuthSession, L as ProxyDCRClient, R as RefreshRequest, y as TOKEN_ENDPOINT_AUTH_METHODS, M as TokenEndpointAuthMethod, N as TokenMapping, Q as TokenRequest, S as TokenResponse, U as UpstreamTokenSet, g as getAuthSession, r as requireAll, e as requireAny, f as requireAuth, h as requireRole, i as requireScopes } from '../OAuthProvider-84BWWDBF.js';
3
3
  import 'node:http';
4
4
 
5
5
  /**
@@ -150,6 +150,14 @@ interface JWTClaims {
150
150
  jti: string;
151
151
  /** Scopes */
152
152
  scope: string[];
153
+ /**
154
+ * Which kind of token this is. Access and refresh tokens share a signing
155
+ * key, audience and claim set, so without this either one verifies as the
156
+ * other: an access token — sent on every request — could be redeemed for a
157
+ * refresh token, and a refresh token presented as a bearer credential.
158
+ * Absent from tokens issued before it existed; those are refused as both.
159
+ */
160
+ token_use?: "access" | "refresh";
153
161
  }
154
162
  /**
155
163
  * JWT Issuer configuration
@@ -18,13 +18,14 @@ import {
18
18
  OAuthProxy,
19
19
  OAuthProxyError,
20
20
  PKCEUtils,
21
+ TOKEN_ENDPOINT_AUTH_METHODS,
21
22
  getAuthSession,
22
23
  requireAll,
23
24
  requireAny,
24
25
  requireAuth,
25
26
  requireRole,
26
27
  requireScopes
27
- } from "../chunk-G44SPVP7.js";
28
+ } from "../chunk-CAQLTSJ5.js";
28
29
  export {
29
30
  AuthProvider,
30
31
  AzureProvider,
@@ -45,6 +46,7 @@ export {
45
46
  OAuthProxy,
46
47
  OAuthProxyError,
47
48
  PKCEUtils,
49
+ TOKEN_ENDPOINT_AUTH_METHODS,
48
50
  getAuthSession,
49
51
  requireAll,
50
52
  requireAny,
@@ -53,6 +53,7 @@ var buildDevCommand = (options) => {
53
53
  // src/bin/validateCommand.ts
54
54
  var _fs = require('fs');
55
55
 
56
+ var _module = require('module');
56
57
 
57
58
  var _url = require('url');
58
59
  var DEFAULT_TYPE_CHECK_OPTIONS = [
@@ -122,8 +123,20 @@ var withTypeCheckProject = async (file, check) => {
122
123
  await _promises.rm.call(void 0, project, { force: true });
123
124
  }
124
125
  };
125
- var buildStructureCheckCommand = (file, execPath = process.execPath) => [
126
+ var resolveTsxLoader = (file) => {
127
+ try {
128
+ return _url.pathToFileURL.call(void 0, _module.createRequire.call(void 0, file).resolve("tsx")).href;
129
+ } catch (e) {
130
+ return void 0;
131
+ }
132
+ };
133
+ var TSX_HINT = "[FastMCP] Hint: if the file imports .ts files with .js extensions, or uses TypeScript syntax Node cannot strip (such as enum), add tsx to your project's devDependencies. validate then loads the file through tsx, as `fastmcp dev` does.";
134
+ var buildStructureCheckCommand = (file, {
135
+ execPath = process.execPath,
136
+ tsxLoader
137
+ } = {}) => [
126
138
  execPath,
139
+ ...tsxLoader ? ["--import", tsxLoader] : [],
127
140
  "-e",
128
141
  STRUCTURE_CHECK_SCRIPT,
129
142
  _url.pathToFileURL.call(void 0, file).href
@@ -295,7 +308,11 @@ await _yargs2.default.call(void 0, _helpers.hideBin.call(void 0, process.argv)).
295
308
  }
296
309
  process.exit(1);
297
310
  }
298
- const [structureCommand, ...structureArgs] = buildStructureCheckCommand(filePath);
311
+ const tsxLoader = resolveTsxLoader(filePath);
312
+ const [structureCommand, ...structureArgs] = buildStructureCheckCommand(
313
+ filePath,
314
+ { tsxLoader }
315
+ );
299
316
  try {
300
317
  await _execa.execa.call(void 0, structureCommand, structureArgs, {
301
318
  stderr: "pipe",
@@ -311,6 +328,9 @@ await _yargs2.default.call(void 0, _helpers.hideBin.call(void 0, process.argv)).
311
328
  "Make sure the file properly imports and uses FastMCP"
312
329
  );
313
330
  }
331
+ if (!tsxLoader) {
332
+ console.error(TSX_HINT);
333
+ }
314
334
  process.exit(1);
315
335
  }
316
336
  console.log(
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/fastmcp/fastmcp/dist/bin/fastmcp.cjs","../../src/bin/fastmcp.ts","../../src/bin/devCommand.ts","../../src/bin/validateCommand.ts"],"names":["yargs"],"mappings":"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","file":"/home/runner/work/fastmcp/fastmcp/dist/bin/fastmcp.cjs","sourcesContent":[null,"#!/usr/bin/env node\n\nimport { execa } from \"execa\";\nimport { mkdtemp, rm, writeFile } from \"fs/promises\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\n\nimport { buildDevCommand, buildDevConfig } from \"./devCommand.js\";\nimport {\n buildStructureCheckCommand,\n buildTypeCheckCommand,\n formatCommandFailure,\n withTypeCheckProject,\n} from \"./validateCommand.js\";\n\nawait yargs(hideBin(process.argv))\n .scriptName(\"fastmcp\")\n .command(\n \"dev <file>\",\n \"Start a development server\",\n (yargs) => {\n return yargs\n .positional(\"file\", {\n demandOption: true,\n describe: \"The path to the server file\",\n type: \"string\",\n })\n\n .option(\"args\", {\n describe:\n \"JSON arguments passed to the tool (requires --tool), e.g. '{\\\"a\\\":1}'\",\n type: \"string\",\n })\n\n .option(\"tool\", {\n alias: \"t\",\n describe:\n \"Call a tool non-interactively instead of launching the inspector\",\n type: \"string\",\n })\n\n .option(\"watch\", {\n alias: \"w\",\n default: false,\n describe: \"Watch for file changes and restart server\",\n type: \"boolean\",\n })\n\n .option(\"verbose\", {\n alias: \"v\",\n default: false,\n describe: \"Enable verbose logging\",\n type: \"boolean\",\n });\n },\n\n async (argv) => {\n let configDir: string | undefined;\n\n try {\n if (argv.args !== undefined && !argv.tool) {\n console.error(\"[FastMCP Error] --args requires --tool\");\n process.exitCode = 1;\n return;\n }\n\n if (argv.tool && argv.watch) {\n console.warn(\n \"[FastMCP] --watch is ignored when calling a tool with --tool: the server runs for a single call.\",\n );\n }\n\n let configPath: string | undefined;\n\n if (argv.tool) {\n configDir = await mkdtemp(join(tmpdir(), \"fastmcp-dev-\"));\n configPath = join(configDir, \"mcp-cli.json\");\n await writeFile(configPath, buildDevConfig(argv.file), \"utf8\");\n }\n\n const [command, ...commandArgs] = buildDevCommand({\n configPath,\n file: argv.file,\n tool: argv.tool,\n toolArgs: argv.args,\n watch: argv.watch,\n });\n\n if (argv.verbose) {\n console.log(\n `[FastMCP] Starting server: ${[command, ...commandArgs].join(\" \")}`,\n );\n console.log(`[FastMCP] File: ${argv.file}`);\n console.log(\n `[FastMCP] Watch mode: ${argv.watch ? \"enabled\" : \"disabled\"}`,\n );\n\n if (argv.tool) {\n console.log(`[FastMCP] Tool: ${argv.tool}`);\n }\n }\n\n await execa(command, commandArgs, {\n stderr: \"inherit\",\n stdin: \"inherit\",\n stdout: \"inherit\",\n });\n } catch (error) {\n console.error(\n \"[FastMCP Error] Failed to start development server:\",\n error instanceof Error ? error.message : String(error),\n );\n\n if (argv.verbose && error instanceof Error && error.stack) {\n console.error(\"[FastMCP Debug] Stack trace:\", error.stack);\n }\n\n // Not process.exit(): that would skip the `finally` block below and\n // leak the temporary config directory on every failed tool call.\n process.exitCode = 1;\n } finally {\n if (configDir) {\n await rm(configDir, { force: true, recursive: true });\n }\n }\n },\n )\n\n .command(\n \"inspect <file>\",\n \"Inspect a server file\",\n (yargs) => {\n return yargs.positional(\"file\", {\n demandOption: true,\n describe: \"The path to the server file\",\n type: \"string\",\n });\n },\n\n async (argv) => {\n try {\n await execa({\n stderr: \"inherit\",\n stdout: \"inherit\",\n })`npx @modelcontextprotocol/inspector npx tsx ${argv.file}`;\n } catch (error) {\n console.error(\n \"[FastMCP Error] Failed to inspect server:\",\n error instanceof Error ? error.message : String(error),\n );\n\n process.exit(1);\n }\n },\n )\n\n .command(\n \"validate <file>\",\n \"Validate a FastMCP server file for syntax and basic structure\",\n (yargs) => {\n return yargs\n .positional(\"file\", {\n demandOption: true,\n describe: \"The path to the server file\",\n type: \"string\",\n })\n\n .option(\"strict\", {\n alias: \"s\",\n default: false,\n describe: \"Enable strict validation (type checking)\",\n type: \"boolean\",\n });\n },\n\n async (argv) => {\n try {\n const { existsSync } = await import(\"fs\");\n const { resolve } = await import(\"path\");\n const filePath = resolve(argv.file);\n\n if (!existsSync(filePath)) {\n console.error(`[FastMCP Error] File not found: ${filePath}`);\n process.exit(1);\n }\n\n console.log(`[FastMCP] Validating server file: ${filePath}`);\n\n try {\n await withTypeCheckProject(filePath, (project) => {\n const [typeCheckCommand, ...typeCheckArgs] = buildTypeCheckCommand(\n filePath,\n { project, strict: argv.strict },\n );\n\n return execa(typeCheckCommand, typeCheckArgs, {\n stderr: \"pipe\",\n stdout: \"pipe\",\n });\n });\n\n console.log(\"[FastMCP] ✓ TypeScript compilation successful\");\n } catch (tsError) {\n console.error(\"[FastMCP] ✗ TypeScript compilation failed\");\n\n const diagnostics = formatCommandFailure(tsError);\n\n if (diagnostics) {\n console.error(diagnostics);\n }\n\n process.exit(1);\n }\n\n const [structureCommand, ...structureArgs] =\n buildStructureCheckCommand(filePath);\n\n try {\n await execa(structureCommand, structureArgs, {\n stderr: \"pipe\",\n stdout: \"pipe\",\n });\n } catch (structureError) {\n // The child reports the reason on stderr, which execa captures. Without\n // replaying it the user only ever sees the generic hint below, even when\n // the server file has an error they could act on.\n const reason = formatCommandFailure(structureError);\n\n if (reason) {\n console.error(reason);\n } else {\n console.error(\"[FastMCP] ✗ Server structure validation failed\");\n console.error(\n \"Make sure the file properly imports and uses FastMCP\",\n );\n }\n\n process.exit(1);\n }\n\n console.log(\n \"[FastMCP] ✓ All validations passed! Server file looks good.\",\n );\n } catch (error) {\n console.error(\n \"[FastMCP Error] Validation failed:\",\n error instanceof Error ? error.message : String(error),\n );\n\n process.exit(1);\n }\n },\n )\n\n .demandCommand(1)\n .strict()\n .help()\n .parseAsync();\n","export type DevCommandOptions = {\n /**\n * Path to a temporary mcp-cli config file that registers the dev server.\n * Only used in non-interactive mode (when `tool` is set).\n */\n configPath?: string;\n /**\n * Path to the server file to run.\n */\n file: string;\n /**\n * Name of the tool to call non-interactively. When omitted, the dev\n * command launches the interactive mcp-cli inspector instead.\n */\n tool?: string;\n /**\n * JSON string of arguments forwarded to the tool via mcp-cli `--args`.\n * Only used in non-interactive mode.\n */\n toolArgs?: string;\n /**\n * Watch the file for changes and restart the server. Only used in\n * interactive mode; a non-interactive call runs the server once.\n */\n watch?: boolean;\n};\n\n/**\n * The server name registered in the generated mcp-cli config. mcp-cli\n * addresses non-interactive targets as `<server-name>:<tool>`.\n */\nexport const DEV_SERVER_NAME = \"dev\";\n\n/**\n * Build the JSON config that registers the dev server with mcp-cli so it can\n * be invoked non-interactively. mcp-cli's non-interactive mode resolves the\n * server by name from a config file's `mcpServers` map, so an inline command\n * cannot be passed directly alongside `call-tool`.\n *\n * The server is never started in watch mode here: a non-interactive call\n * connects, calls one tool, and exits, so there is nothing to restart into.\n */\nexport const buildDevConfig = (file: string): string =>\n JSON.stringify(\n {\n mcpServers: {\n [DEV_SERVER_NAME]: {\n args: [\"tsx\", file],\n command: \"npx\",\n },\n },\n },\n null,\n 2,\n );\n\n/**\n * Build the argv that the dev command runs.\n *\n * This is an argv array rather than a shell string so that nothing has to be\n * quoted: file paths, tool names and JSON `--args` are passed to the child\n * process verbatim. Quoting through a shell would be wrong on Windows, where\n * cmd.exe treats single quotes as literal characters rather than as quoting.\n *\n * - Without `tool`: launches the interactive @wong2/mcp-cli inspector\n * against an inline `npx tsx <file>` server (existing behaviour).\n * - With `tool`: runs @wong2/mcp-cli in non-interactive mode against a\n * generated config file, calling the named tool and forwarding `--args`.\n */\nexport const buildDevCommand = (\n options: DevCommandOptions,\n): [string, ...string[]] => {\n const { configPath, file, tool, toolArgs, watch = false } = options;\n\n if (tool) {\n if (!configPath) {\n throw new Error(\n \"configPath is required when calling a tool non-interactively\",\n );\n }\n\n return [\n \"npx\",\n \"@wong2/mcp-cli\",\n \"-c\",\n configPath,\n \"call-tool\",\n `${DEV_SERVER_NAME}:${tool}`,\n ...(toolArgs === undefined ? [] : [\"--args\", toolArgs]),\n ];\n }\n\n return [\n \"npx\",\n \"@wong2/mcp-cli\",\n \"npx\",\n \"tsx\",\n ...(watch ? [\"--watch\"] : []),\n file,\n ];\n};\n","import { existsSync } from \"node:fs\";\nimport { rm, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\n/**\n * Compiler options for type-checking a server file with no `tsconfig.json`\n * above it. tsc's own defaults (ES5, CommonJS-era module resolution) cannot\n * even read FastMCP's declarations, which use private class fields and a\n * default import of `http`, so no real server file could pass under them.\n * These describe how a server actually runs instead: as an ES module on a\n * current Node, which can import `.ts` files directly.\n */\nexport const DEFAULT_TYPE_CHECK_OPTIONS = [\n \"--allowImportingTsExtensions\",\n \"--module\",\n \"esnext\",\n \"--moduleResolution\",\n \"bundler\",\n \"--skipLibCheck\",\n \"--target\",\n \"es2022\",\n];\n\n/**\n * The script that `fastmcp validate` runs to check a server file can be\n * imported.\n *\n * The target file is read from `process.argv[1]` instead of being spliced into\n * this source. Splicing is not equivalent: the path would land inside a\n * JavaScript string literal, so the child's own parser would consume the\n * backslashes of a Windows path as string escapes. `C:\\Users\\dev\\server.ts`\n * reaches the import as `C:Usersdevserver.ts`, which throws `ERR_INVALID_URL`\n * and is reported to the user as \"does not import FastMCP\" even though the file\n * is fine.\n *\n * The script exits explicitly once the import settles. Importing a server file\n * normally starts the server, and a running server — a stdio transport waiting\n * on stdin, an HTTP listener — keeps the process alive indefinitely, so\n * validating a server that calls `start()` would otherwise never finish.\n */\nexport const STRUCTURE_CHECK_SCRIPT = `\n(async () => {\n try {\n const { FastMCP } = await import(\"fastmcp\");\n await import(process.argv[1]);\n console.log(\"[FastMCP] ✓ Server structure validation passed\");\n } catch (error) {\n console.error(\"[FastMCP] ✗ Server structure validation failed:\", error.message);\n process.exitCode = 1;\n }\n\n process.exit();\n})();\n`;\n\n/**\n * Build the argv for the type-check step.\n *\n * This is an argv array rather than a shell string so that nothing has to be\n * quoted: the file path is passed to tsc verbatim, so a path containing spaces\n * stays one argument. Quoting through a shell would be wrong on Windows, where\n * cmd.exe re-parses the command line and splits such a path into several root\n * files.\n *\n * Given a `project` (see `withTypeCheckProject`), tsc checks the file through\n * it; without one, the file is passed directly with\n * `DEFAULT_TYPE_CHECK_OPTIONS`.\n */\nexport const buildTypeCheckCommand = (\n file: string,\n { project, strict = false }: { project?: string; strict?: boolean } = {},\n): [string, ...string[]] => [\n \"npx\",\n \"tsc\",\n \"--noEmit\",\n ...(strict ? [\"--strict\"] : []),\n ...(project ? [\"--project\", project] : [...DEFAULT_TYPE_CHECK_OPTIONS, file]),\n];\n\n/**\n * Build a tsconfig that checks only `file`, with the compiler options of the\n * project's own `tsconfig`.\n *\n * `tsc <file>` ignores tsconfig.json altogether and falls back to tsc's\n * defaults, while `tsc --project tsconfig.json` checks every file in the\n * project rather than the one being validated. Extending the project's config\n * and listing just `file` gets both right. A project that is `incremental` or\n * `composite` still writes a `.tsbuildinfo` under `--noEmit`, so both are\n * switched off: validating a file should leave the project untouched. Disable\n * declaration output options too, since they may rely on composite's implicit\n * `declaration: true`; `declarationDir` takes a path, so `null` unsets it.\n */\nexport const buildTypeCheckProject = (tsconfig: string, file: string): string =>\n JSON.stringify({\n compilerOptions: {\n composite: false,\n declarationDir: null,\n declarationMap: false,\n emitDeclarationOnly: false,\n incremental: false,\n },\n extends: `./${basename(tsconfig)}`,\n files: [file],\n include: [],\n });\n\n/**\n * Find the `tsconfig.json` that applies to files in `dir`: the nearest one in\n * it or any of its parents, which is where tsc and editors look too.\n */\nexport const findTsconfig = (dir: string): string | undefined => {\n const candidate = join(dir, \"tsconfig.json\");\n\n if (existsSync(candidate)) {\n return candidate;\n }\n\n const parent = dirname(dir);\n\n return parent === dir ? undefined : findTsconfig(parent);\n};\n\n/**\n * Run `check` with a throwaway tsconfig for `file` (see\n * `buildTypeCheckProject`), or with no project when the file has no\n * `tsconfig.json` above it.\n *\n * The throwaway config is written next to the one it extends and removed once\n * `check` settles. It cannot live in a temporary directory: tsc looks for the\n * `@types` packages it includes by default relative to the config file, so\n * from anywhere else a server using, say, `process.env` would stop compiling.\n */\nexport const withTypeCheckProject = async <T>(\n file: string,\n check: (project?: string) => Promise<T>,\n): Promise<T> => {\n const tsconfig = findTsconfig(dirname(file));\n\n if (!tsconfig) {\n return check();\n }\n\n const project = join(\n dirname(tsconfig),\n `tsconfig.fastmcp-validate-${process.pid}.json`,\n );\n\n await writeFile(project, buildTypeCheckProject(tsconfig, file), \"utf8\");\n\n try {\n return await check(project);\n } finally {\n await rm(project, { force: true });\n }\n};\n\n/**\n * Build the argv for the structure-check step.\n *\n * As above, the file is passed as an argument rather than quoted into a shell\n * command. It is converted to a file URL by the parent process, so the child\n * receives the URL as-is and never has to reconstruct it. The child is the same\n * Node binary running the CLI, so no PATH lookup is involved.\n */\nexport const buildStructureCheckCommand = (\n file: string,\n execPath: string = process.execPath,\n): [string, ...string[]] => [\n execPath,\n \"-e\",\n STRUCTURE_CHECK_SCRIPT,\n pathToFileURL(file).href,\n];\n\n/**\n * The output streams a failed command carries. execa sets both on the error it\n * throws, but they are optional here so the extraction stays honest about not\n * every `Error` having them.\n */\ntype CommandOutputStreams = {\n stderr?: unknown;\n stdout?: unknown;\n};\n\nconst hasOutputStreams = (\n error: Error,\n): error is CommandOutputStreams & Error =>\n \"stdout\" in error || \"stderr\" in error;\n\n/**\n * Extract the output of a failed command.\n *\n * tsc writes its diagnostics to stdout, while a failure to launch the compiler\n * at all (a missing `npx`, for instance) reports on stderr — reading only\n * stderr left the message empty for every real type error. The structure check\n * reports the reason on stderr, which execa captures, so it has to be replayed\n * here as well or the caller can only print a generic hint.\n *\n * An error that did not come from a command at all — the throwaway tsconfig\n * could not be written, say — is reported by its message.\n */\nexport const formatCommandFailure = (error: unknown): string => {\n if (!(error instanceof Error)) {\n return \"\";\n }\n\n if (!hasOutputStreams(error)) {\n return error.message;\n }\n\n const collect = (value: unknown): string =>\n typeof value === \"string\" ? value.trim() : \"\";\n\n return [collect(error.stderr), collect(error.stdout)]\n .filter(Boolean)\n .join(\"\\n\");\n};\n"]}
1
+ {"version":3,"sources":["/home/runner/work/fastmcp/fastmcp/dist/bin/fastmcp.cjs","../../src/bin/fastmcp.ts","../../src/bin/devCommand.ts","../../src/bin/validateCommand.ts"],"names":["yargs"],"mappings":"AAAA;AACA;AACA;ACAA,8BAAsB;AACtB,uCAAuC;AACvC,wBAAuB;AACvB,4BAAqB;AACrB,4EAAkB;AAClB,wCAAwB;ADExB;AACA;AEqBO,IAAM,gBAAA,EAAkB,KAAA;AAWxB,IAAM,eAAA,EAAiB,CAAC,IAAA,EAAA,GAC7B,IAAA,CAAK,SAAA;AAAA,EACH;AAAA,IACE,UAAA,EAAY;AAAA,MACV,CAAC,eAAe,CAAA,EAAG;AAAA,QACjB,IAAA,EAAM,CAAC,KAAA,EAAO,IAAI,CAAA;AAAA,QAClB,OAAA,EAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,CAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA;AAeK,IAAM,gBAAA,EAAkB,CAC7B,OAAA,EAAA,GAC0B;AAC1B,EAAA,MAAM,EAAE,UAAA,EAAY,IAAA,EAAM,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,MAAM,EAAA,EAAI,OAAA;AAE5D,EAAA,GAAA,CAAI,IAAA,EAAM;AACR,IAAA,GAAA,CAAI,CAAC,UAAA,EAAY;AACf,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,MACF,CAAA;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,KAAA;AAAA,MACA,gBAAA;AAAA,MACA,IAAA;AAAA,MACA,UAAA;AAAA,MACA,WAAA;AAAA,MACA,CAAA,EAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACJ,IAAA;AACF,EAAA;AACF;AFjDO;AACA;AGpDP;AACA;AACA;AACA;AACA;AAUa;AACX,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF;AAmBa;AAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BzB;AAIX,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACN;AAea;AAET,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAMU;AACX,EAAA;AAEI,EAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AAEA,EAAA;AACF;AAYa;AAIX,EAAA;AAEK,EAAA;AACH,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACA,IAAA;AACF,EAAA;AAEA,EAAA;AAEI,EAAA;AACF,IAAA;AACF,EAAA;AACE,IAAA;AACF,EAAA;AACF;AAea;AACP,EAAA;AACF,IAAA;AACF,EAAA;AACE,IAAA;AACF,EAAA;AACF;AAMa;AAaA;AAGT,EAAA;AACA,EAAA;AAC8C;AAEhD,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACF;AAYM;AAiBO;AACP,EAAA;AACF,IAAA;AACF,EAAA;AAEK,EAAA;AACH,IAAA;AACF,EAAA;AAEA,EAAA;AAGA,EAAA;AAGF;AHlGO;AACA;ACxID;AAGF,EAAA;AACA,EAAA;AACCA,EAAAA;AACC,IAAA;AAEI,MAAA;AACA,MAAA;AACA,MAAA;AAGD,IAAA;AACC,MAAA;AAEA,MAAA;AAGD,IAAA;AACC,MAAA;AACA,MAAA;AAEA,MAAA;AAGD,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAGD,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACL,EAAA;AAEA,EAAA;AACE,IAAA;AAEA,IAAA;AACE,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AAEA,MAAA;AACE,QAAA;AAAQ,UAAA;AACN,QAAA;AAEJ,MAAA;AAEA,MAAA;AAEA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AAEA,MAAA;AAAkD,QAAA;AAChD,QAAA;AACW,QAAA;AACA,QAAA;AACI,QAAA;AAEhB,MAAA;AAED,MAAA;AACE,QAAA;AAAQ,UAAA;AAC2D,QAAA;AAEnE,QAAA;AACA,QAAA;AAAQ,UAAA;AACsD,QAAA;AAG9D,QAAA;AACE,UAAA;AAA0C,QAAA;AAE9C,MAAA;AAEA,MAAA;AAAkC,QAAA;AACxB,QAAA;AACD,QAAA;AAER,MAAA;AACH,IAAA;AACE,MAAA;AAAQ,QAAA;AACN,QAAA;AAEF,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AAIA,MAAA;AACF,IAAA;AACE,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAGD;AACC,EAAA;AACA,EAAA;AACCA,EAAAA;AACC,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AAAY,QAAA;AACF,QAAA;AAET,MAAA;AACH,IAAA;AACE,MAAA;AAAQ,QAAA;AACN,QAAA;AAEF,MAAA;AAEA,MAAA;AACF,IAAA;AACF,EAAA;AAGD;AACC,EAAA;AACA,EAAA;AACCA,EAAAA;AACC,IAAA;AAEI,MAAA;AACA,MAAA;AACA,MAAA;AAGD,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACL,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AAEA,MAAA;AAEA,MAAA;AACE,QAAA;AACE,UAAA;AAA6C,YAAA;AAC3C,YAAA;AAC+B,UAAA;AAGjC,UAAA;AAA8C,YAAA;AACpC,YAAA;AACA,UAAA;AACT,QAAA;AAGH,QAAA;AACF,MAAA;AACE,QAAA;AAEA,QAAA;AAEA,QAAA;AACE,UAAA;AAAyB,QAAA;AAG3B,QAAA;AACF,MAAA;AAEA,MAAA;AACA,MAAA;AAA6C,QAAA;AAC3C,QAAA;AAEF,MAAA;AAEA,MAAA;AACE,QAAA;AAA6C,UAAA;AACnC,UAAA;AACA,QAAA;AAEZ,MAAA;AAIE,QAAA;AAEA,QAAA;AACE,UAAA;AAAoB,QAAA;AAEpB,UAAA;AACA,UAAA;AAAQ,YAAA;AACN,UAAA;AACF,QAAA;AAGF,QAAA;AACE,UAAA;AAAsB,QAAA;AAGxB,QAAA;AACF,MAAA;AAEA,MAAA;AAAQ,QAAA;AAER,MAAA;AACF,IAAA;AACE,MAAA;AAAQ,QAAA;AACN,QAAA;AAEF,MAAA;AAEA,MAAA;AACF,IAAA;AACF,EAAA;AAGD","file":"/home/runner/work/fastmcp/fastmcp/dist/bin/fastmcp.cjs","sourcesContent":[null,"#!/usr/bin/env node\n\nimport { execa } from \"execa\";\nimport { mkdtemp, rm, writeFile } from \"fs/promises\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\n\nimport { buildDevCommand, buildDevConfig } from \"./devCommand.js\";\nimport {\n buildStructureCheckCommand,\n buildTypeCheckCommand,\n formatCommandFailure,\n resolveTsxLoader,\n TSX_HINT,\n withTypeCheckProject,\n} from \"./validateCommand.js\";\n\nawait yargs(hideBin(process.argv))\n .scriptName(\"fastmcp\")\n .command(\n \"dev <file>\",\n \"Start a development server\",\n (yargs) => {\n return yargs\n .positional(\"file\", {\n demandOption: true,\n describe: \"The path to the server file\",\n type: \"string\",\n })\n\n .option(\"args\", {\n describe:\n \"JSON arguments passed to the tool (requires --tool), e.g. '{\\\"a\\\":1}'\",\n type: \"string\",\n })\n\n .option(\"tool\", {\n alias: \"t\",\n describe:\n \"Call a tool non-interactively instead of launching the inspector\",\n type: \"string\",\n })\n\n .option(\"watch\", {\n alias: \"w\",\n default: false,\n describe: \"Watch for file changes and restart server\",\n type: \"boolean\",\n })\n\n .option(\"verbose\", {\n alias: \"v\",\n default: false,\n describe: \"Enable verbose logging\",\n type: \"boolean\",\n });\n },\n\n async (argv) => {\n let configDir: string | undefined;\n\n try {\n if (argv.args !== undefined && !argv.tool) {\n console.error(\"[FastMCP Error] --args requires --tool\");\n process.exitCode = 1;\n return;\n }\n\n if (argv.tool && argv.watch) {\n console.warn(\n \"[FastMCP] --watch is ignored when calling a tool with --tool: the server runs for a single call.\",\n );\n }\n\n let configPath: string | undefined;\n\n if (argv.tool) {\n configDir = await mkdtemp(join(tmpdir(), \"fastmcp-dev-\"));\n configPath = join(configDir, \"mcp-cli.json\");\n await writeFile(configPath, buildDevConfig(argv.file), \"utf8\");\n }\n\n const [command, ...commandArgs] = buildDevCommand({\n configPath,\n file: argv.file,\n tool: argv.tool,\n toolArgs: argv.args,\n watch: argv.watch,\n });\n\n if (argv.verbose) {\n console.log(\n `[FastMCP] Starting server: ${[command, ...commandArgs].join(\" \")}`,\n );\n console.log(`[FastMCP] File: ${argv.file}`);\n console.log(\n `[FastMCP] Watch mode: ${argv.watch ? \"enabled\" : \"disabled\"}`,\n );\n\n if (argv.tool) {\n console.log(`[FastMCP] Tool: ${argv.tool}`);\n }\n }\n\n await execa(command, commandArgs, {\n stderr: \"inherit\",\n stdin: \"inherit\",\n stdout: \"inherit\",\n });\n } catch (error) {\n console.error(\n \"[FastMCP Error] Failed to start development server:\",\n error instanceof Error ? error.message : String(error),\n );\n\n if (argv.verbose && error instanceof Error && error.stack) {\n console.error(\"[FastMCP Debug] Stack trace:\", error.stack);\n }\n\n // Not process.exit(): that would skip the `finally` block below and\n // leak the temporary config directory on every failed tool call.\n process.exitCode = 1;\n } finally {\n if (configDir) {\n await rm(configDir, { force: true, recursive: true });\n }\n }\n },\n )\n\n .command(\n \"inspect <file>\",\n \"Inspect a server file\",\n (yargs) => {\n return yargs.positional(\"file\", {\n demandOption: true,\n describe: \"The path to the server file\",\n type: \"string\",\n });\n },\n\n async (argv) => {\n try {\n await execa({\n stderr: \"inherit\",\n stdout: \"inherit\",\n })`npx @modelcontextprotocol/inspector npx tsx ${argv.file}`;\n } catch (error) {\n console.error(\n \"[FastMCP Error] Failed to inspect server:\",\n error instanceof Error ? error.message : String(error),\n );\n\n process.exit(1);\n }\n },\n )\n\n .command(\n \"validate <file>\",\n \"Validate a FastMCP server file for syntax and basic structure\",\n (yargs) => {\n return yargs\n .positional(\"file\", {\n demandOption: true,\n describe: \"The path to the server file\",\n type: \"string\",\n })\n\n .option(\"strict\", {\n alias: \"s\",\n default: false,\n describe: \"Enable strict validation (type checking)\",\n type: \"boolean\",\n });\n },\n\n async (argv) => {\n try {\n const { existsSync } = await import(\"fs\");\n const { resolve } = await import(\"path\");\n const filePath = resolve(argv.file);\n\n if (!existsSync(filePath)) {\n console.error(`[FastMCP Error] File not found: ${filePath}`);\n process.exit(1);\n }\n\n console.log(`[FastMCP] Validating server file: ${filePath}`);\n\n try {\n await withTypeCheckProject(filePath, (project) => {\n const [typeCheckCommand, ...typeCheckArgs] = buildTypeCheckCommand(\n filePath,\n { project, strict: argv.strict },\n );\n\n return execa(typeCheckCommand, typeCheckArgs, {\n stderr: \"pipe\",\n stdout: \"pipe\",\n });\n });\n\n console.log(\"[FastMCP] ✓ TypeScript compilation successful\");\n } catch (tsError) {\n console.error(\"[FastMCP] ✗ TypeScript compilation failed\");\n\n const diagnostics = formatCommandFailure(tsError);\n\n if (diagnostics) {\n console.error(diagnostics);\n }\n\n process.exit(1);\n }\n\n const tsxLoader = resolveTsxLoader(filePath);\n const [structureCommand, ...structureArgs] = buildStructureCheckCommand(\n filePath,\n { tsxLoader },\n );\n\n try {\n await execa(structureCommand, structureArgs, {\n stderr: \"pipe\",\n stdout: \"pipe\",\n });\n } catch (structureError) {\n // The child reports the reason on stderr, which execa captures. Without\n // replaying it the user only ever sees the generic hint below, even when\n // the server file has an error they could act on.\n const reason = formatCommandFailure(structureError);\n\n if (reason) {\n console.error(reason);\n } else {\n console.error(\"[FastMCP] ✗ Server structure validation failed\");\n console.error(\n \"Make sure the file properly imports and uses FastMCP\",\n );\n }\n\n if (!tsxLoader) {\n console.error(TSX_HINT);\n }\n\n process.exit(1);\n }\n\n console.log(\n \"[FastMCP] ✓ All validations passed! Server file looks good.\",\n );\n } catch (error) {\n console.error(\n \"[FastMCP Error] Validation failed:\",\n error instanceof Error ? error.message : String(error),\n );\n\n process.exit(1);\n }\n },\n )\n\n .demandCommand(1)\n .strict()\n .help()\n .parseAsync();\n","export type DevCommandOptions = {\n /**\n * Path to a temporary mcp-cli config file that registers the dev server.\n * Only used in non-interactive mode (when `tool` is set).\n */\n configPath?: string;\n /**\n * Path to the server file to run.\n */\n file: string;\n /**\n * Name of the tool to call non-interactively. When omitted, the dev\n * command launches the interactive mcp-cli inspector instead.\n */\n tool?: string;\n /**\n * JSON string of arguments forwarded to the tool via mcp-cli `--args`.\n * Only used in non-interactive mode.\n */\n toolArgs?: string;\n /**\n * Watch the file for changes and restart the server. Only used in\n * interactive mode; a non-interactive call runs the server once.\n */\n watch?: boolean;\n};\n\n/**\n * The server name registered in the generated mcp-cli config. mcp-cli\n * addresses non-interactive targets as `<server-name>:<tool>`.\n */\nexport const DEV_SERVER_NAME = \"dev\";\n\n/**\n * Build the JSON config that registers the dev server with mcp-cli so it can\n * be invoked non-interactively. mcp-cli's non-interactive mode resolves the\n * server by name from a config file's `mcpServers` map, so an inline command\n * cannot be passed directly alongside `call-tool`.\n *\n * The server is never started in watch mode here: a non-interactive call\n * connects, calls one tool, and exits, so there is nothing to restart into.\n */\nexport const buildDevConfig = (file: string): string =>\n JSON.stringify(\n {\n mcpServers: {\n [DEV_SERVER_NAME]: {\n args: [\"tsx\", file],\n command: \"npx\",\n },\n },\n },\n null,\n 2,\n );\n\n/**\n * Build the argv that the dev command runs.\n *\n * This is an argv array rather than a shell string so that nothing has to be\n * quoted: file paths, tool names and JSON `--args` are passed to the child\n * process verbatim. Quoting through a shell would be wrong on Windows, where\n * cmd.exe treats single quotes as literal characters rather than as quoting.\n *\n * - Without `tool`: launches the interactive @wong2/mcp-cli inspector\n * against an inline `npx tsx <file>` server (existing behaviour).\n * - With `tool`: runs @wong2/mcp-cli in non-interactive mode against a\n * generated config file, calling the named tool and forwarding `--args`.\n */\nexport const buildDevCommand = (\n options: DevCommandOptions,\n): [string, ...string[]] => {\n const { configPath, file, tool, toolArgs, watch = false } = options;\n\n if (tool) {\n if (!configPath) {\n throw new Error(\n \"configPath is required when calling a tool non-interactively\",\n );\n }\n\n return [\n \"npx\",\n \"@wong2/mcp-cli\",\n \"-c\",\n configPath,\n \"call-tool\",\n `${DEV_SERVER_NAME}:${tool}`,\n ...(toolArgs === undefined ? [] : [\"--args\", toolArgs]),\n ];\n }\n\n return [\n \"npx\",\n \"@wong2/mcp-cli\",\n \"npx\",\n \"tsx\",\n ...(watch ? [\"--watch\"] : []),\n file,\n ];\n};\n","import { existsSync } from \"node:fs\";\nimport { rm, writeFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { basename, dirname, join } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\n/**\n * Compiler options for type-checking a server file with no `tsconfig.json`\n * above it. tsc's own defaults (ES5, CommonJS-era module resolution) cannot\n * even read FastMCP's declarations, which use private class fields and a\n * default import of `http`, so no real server file could pass under them.\n * These describe how a server actually runs instead: as an ES module on a\n * current Node, which can import `.ts` files directly.\n */\nexport const DEFAULT_TYPE_CHECK_OPTIONS = [\n \"--allowImportingTsExtensions\",\n \"--module\",\n \"esnext\",\n \"--moduleResolution\",\n \"bundler\",\n \"--skipLibCheck\",\n \"--target\",\n \"es2022\",\n];\n\n/**\n * The script that `fastmcp validate` runs to check a server file can be\n * imported.\n *\n * The target file is read from `process.argv[1]` instead of being spliced into\n * this source. Splicing is not equivalent: the path would land inside a\n * JavaScript string literal, so the child's own parser would consume the\n * backslashes of a Windows path as string escapes. `C:\\Users\\dev\\server.ts`\n * reaches the import as `C:Usersdevserver.ts`, which throws `ERR_INVALID_URL`\n * and is reported to the user as \"does not import FastMCP\" even though the file\n * is fine.\n *\n * The script exits explicitly once the import settles. Importing a server file\n * normally starts the server, and a running server — a stdio transport waiting\n * on stdin, an HTTP listener — keeps the process alive indefinitely, so\n * validating a server that calls `start()` would otherwise never finish.\n */\nexport const STRUCTURE_CHECK_SCRIPT = `\n(async () => {\n try {\n const { FastMCP } = await import(\"fastmcp\");\n await import(process.argv[1]);\n console.log(\"[FastMCP] ✓ Server structure validation passed\");\n } catch (error) {\n console.error(\"[FastMCP] ✗ Server structure validation failed:\", error.message);\n process.exitCode = 1;\n }\n\n process.exit();\n})();\n`;\n\n/**\n * Build the argv for the type-check step.\n *\n * This is an argv array rather than a shell string so that nothing has to be\n * quoted: the file path is passed to tsc verbatim, so a path containing spaces\n * stays one argument. Quoting through a shell would be wrong on Windows, where\n * cmd.exe re-parses the command line and splits such a path into several root\n * files.\n *\n * Given a `project` (see `withTypeCheckProject`), tsc checks the file through\n * it; without one, the file is passed directly with\n * `DEFAULT_TYPE_CHECK_OPTIONS`.\n */\nexport const buildTypeCheckCommand = (\n file: string,\n { project, strict = false }: { project?: string; strict?: boolean } = {},\n): [string, ...string[]] => [\n \"npx\",\n \"tsc\",\n \"--noEmit\",\n ...(strict ? [\"--strict\"] : []),\n ...(project ? [\"--project\", project] : [...DEFAULT_TYPE_CHECK_OPTIONS, file]),\n];\n\n/**\n * Build a tsconfig that checks only `file`, with the compiler options of the\n * project's own `tsconfig`.\n *\n * `tsc <file>` ignores tsconfig.json altogether and falls back to tsc's\n * defaults, while `tsc --project tsconfig.json` checks every file in the\n * project rather than the one being validated. Extending the project's config\n * and listing just `file` gets both right. A project that is `incremental` or\n * `composite` still writes a `.tsbuildinfo` under `--noEmit`, so both are\n * switched off: validating a file should leave the project untouched. Disable\n * declaration output options too, since they may rely on composite's implicit\n * `declaration: true`; `declarationDir` takes a path, so `null` unsets it.\n */\nexport const buildTypeCheckProject = (tsconfig: string, file: string): string =>\n JSON.stringify({\n compilerOptions: {\n composite: false,\n declarationDir: null,\n declarationMap: false,\n emitDeclarationOnly: false,\n incremental: false,\n },\n extends: `./${basename(tsconfig)}`,\n files: [file],\n include: [],\n });\n\n/**\n * Find the `tsconfig.json` that applies to files in `dir`: the nearest one in\n * it or any of its parents, which is where tsc and editors look too.\n */\nexport const findTsconfig = (dir: string): string | undefined => {\n const candidate = join(dir, \"tsconfig.json\");\n\n if (existsSync(candidate)) {\n return candidate;\n }\n\n const parent = dirname(dir);\n\n return parent === dir ? undefined : findTsconfig(parent);\n};\n\n/**\n * Run `check` with a throwaway tsconfig for `file` (see\n * `buildTypeCheckProject`), or with no project when the file has no\n * `tsconfig.json` above it.\n *\n * The throwaway config is written next to the one it extends and removed once\n * `check` settles. It cannot live in a temporary directory: tsc looks for the\n * `@types` packages it includes by default relative to the config file, so\n * from anywhere else a server using, say, `process.env` would stop compiling.\n */\nexport const withTypeCheckProject = async <T>(\n file: string,\n check: (project?: string) => Promise<T>,\n): Promise<T> => {\n const tsconfig = findTsconfig(dirname(file));\n\n if (!tsconfig) {\n return check();\n }\n\n const project = join(\n dirname(tsconfig),\n `tsconfig.fastmcp-validate-${process.pid}.json`,\n );\n\n await writeFile(project, buildTypeCheckProject(tsconfig, file), \"utf8\");\n\n try {\n return await check(project);\n } finally {\n await rm(project, { force: true });\n }\n};\n\n/**\n * Find tsx in the project `file` belongs to, as the file: URL of its loader.\n *\n * `fastmcp dev` runs a server with tsx. Node alone only strips types: it\n * cannot follow the `.js` import of a `.ts` file that TypeScript itself asks\n * for, nor run syntax that has to be transformed, like `enum` — and since Node\n * 26 no flag makes it. So the structure check loads the file through the\n * project's tsx when there is one, the way `dev` runs it.\n *\n * Resolved here rather than by the child: `--import tsx` would resolve from\n * the child's working directory, and fail the whole check wherever tsx is not\n * installed. A URL, because `--import` reads a Windows path as a URL scheme.\n */\nexport const resolveTsxLoader = (file: string): string | undefined => {\n try {\n return pathToFileURL(createRequire(file).resolve(\"tsx\")).href;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Printed when the structure check fails and the project has no tsx, so the\n * file was loaded by Node alone (see `resolveTsxLoader`).\n */\nexport const TSX_HINT =\n \"[FastMCP] Hint: if the file imports .ts files with .js extensions, or uses TypeScript syntax Node cannot strip (such as enum), add tsx to your project's devDependencies. validate then loads the file through tsx, as `fastmcp dev` does.\";\n\n/**\n * Build the argv for the structure-check step.\n *\n * As above, the file is passed as an argument rather than quoted into a shell\n * command. It is converted to a file URL by the parent process, so the child\n * receives the URL as-is and never has to reconstruct it. The child is the same\n * Node binary running the CLI, so no PATH lookup is involved.\n *\n * Given a `tsxLoader` (see `resolveTsxLoader`), the child loads it first.\n */\nexport const buildStructureCheckCommand = (\n file: string,\n {\n execPath = process.execPath,\n tsxLoader,\n }: { execPath?: string; tsxLoader?: string } = {},\n): [string, ...string[]] => [\n execPath,\n ...(tsxLoader ? [\"--import\", tsxLoader] : []),\n \"-e\",\n STRUCTURE_CHECK_SCRIPT,\n pathToFileURL(file).href,\n];\n\n/**\n * The output streams a failed command carries. execa sets both on the error it\n * throws, but they are optional here so the extraction stays honest about not\n * every `Error` having them.\n */\ntype CommandOutputStreams = {\n stderr?: unknown;\n stdout?: unknown;\n};\n\nconst hasOutputStreams = (\n error: Error,\n): error is CommandOutputStreams & Error =>\n \"stdout\" in error || \"stderr\" in error;\n\n/**\n * Extract the output of a failed command.\n *\n * tsc writes its diagnostics to stdout, while a failure to launch the compiler\n * at all (a missing `npx`, for instance) reports on stderr — reading only\n * stderr left the message empty for every real type error. The structure check\n * reports the reason on stderr, which execa captures, so it has to be replayed\n * here as well or the caller can only print a generic hint.\n *\n * An error that did not come from a command at all — the throwaway tsconfig\n * could not be written, say — is reported by its message.\n */\nexport const formatCommandFailure = (error: unknown): string => {\n if (!(error instanceof Error)) {\n return \"\";\n }\n\n if (!hasOutputStreams(error)) {\n return error.message;\n }\n\n const collect = (value: unknown): string =>\n typeof value === \"string\" ? value.trim() : \"\";\n\n return [collect(error.stderr), collect(error.stdout)]\n .filter(Boolean)\n .join(\"\\n\");\n};\n"]}
@@ -53,6 +53,7 @@ var buildDevCommand = (options) => {
53
53
  // src/bin/validateCommand.ts
54
54
  import { existsSync } from "fs";
55
55
  import { rm, writeFile } from "fs/promises";
56
+ import { createRequire } from "module";
56
57
  import { basename, dirname, join } from "path";
57
58
  import { pathToFileURL } from "url";
58
59
  var DEFAULT_TYPE_CHECK_OPTIONS = [
@@ -122,8 +123,20 @@ var withTypeCheckProject = async (file, check) => {
122
123
  await rm(project, { force: true });
123
124
  }
124
125
  };
125
- var buildStructureCheckCommand = (file, execPath = process.execPath) => [
126
+ var resolveTsxLoader = (file) => {
127
+ try {
128
+ return pathToFileURL(createRequire(file).resolve("tsx")).href;
129
+ } catch {
130
+ return void 0;
131
+ }
132
+ };
133
+ var TSX_HINT = "[FastMCP] Hint: if the file imports .ts files with .js extensions, or uses TypeScript syntax Node cannot strip (such as enum), add tsx to your project's devDependencies. validate then loads the file through tsx, as `fastmcp dev` does.";
134
+ var buildStructureCheckCommand = (file, {
135
+ execPath = process.execPath,
136
+ tsxLoader
137
+ } = {}) => [
126
138
  execPath,
139
+ ...tsxLoader ? ["--import", tsxLoader] : [],
127
140
  "-e",
128
141
  STRUCTURE_CHECK_SCRIPT,
129
142
  pathToFileURL(file).href
@@ -295,7 +308,11 @@ await yargs(hideBin(process.argv)).scriptName("fastmcp").command(
295
308
  }
296
309
  process.exit(1);
297
310
  }
298
- const [structureCommand, ...structureArgs] = buildStructureCheckCommand(filePath);
311
+ const tsxLoader = resolveTsxLoader(filePath);
312
+ const [structureCommand, ...structureArgs] = buildStructureCheckCommand(
313
+ filePath,
314
+ { tsxLoader }
315
+ );
299
316
  try {
300
317
  await execa(structureCommand, structureArgs, {
301
318
  stderr: "pipe",
@@ -311,6 +328,9 @@ await yargs(hideBin(process.argv)).scriptName("fastmcp").command(
311
328
  "Make sure the file properly imports and uses FastMCP"
312
329
  );
313
330
  }
331
+ if (!tsxLoader) {
332
+ console.error(TSX_HINT);
333
+ }
314
334
  process.exit(1);
315
335
  }
316
336
  console.log(