fastmcp 3.23.1 → 3.24.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/README.md +43 -2
- package/dist/FastMCP.d.ts +11 -0
- package/dist/FastMCP.js +154 -0
- package/dist/FastMCP.js.map +1 -1
- package/dist/OAuthProxy-BOCkkAhO.d.ts +519 -0
- package/dist/auth/index.d.ts +445 -0
- package/dist/auth/index.js +1844 -0
- package/dist/auth/index.js.map +1 -0
- package/package.json +21 -2
package/README.md
CHANGED
|
@@ -1311,9 +1311,50 @@ server.addTool({
|
|
|
1311
1311
|
|
|
1312
1312
|
In this example, only clients authenticating with the `admin` role will be able to list or call the `admin-dashboard` tool. The `public-info` tool will be available to all authenticated users.
|
|
1313
1313
|
|
|
1314
|
-
#### OAuth
|
|
1314
|
+
#### OAuth Proxy
|
|
1315
1315
|
|
|
1316
|
-
FastMCP includes built-in
|
|
1316
|
+
FastMCP includes a built-in **OAuth Proxy** that acts as a secure intermediary between MCP clients and upstream OAuth providers. The proxy handles the complete OAuth 2.1 authorization flow, including Dynamic Client Registration (DCR), PKCE, consent management, and token management with encryption and token swap patterns enabled by default.
|
|
1317
|
+
|
|
1318
|
+
**Key Features:**
|
|
1319
|
+
|
|
1320
|
+
- 🔐 **Secure by Default**: Automatic encryption (AES-256-GCM) and token swap pattern
|
|
1321
|
+
- 🚀 **Zero Configuration**: Auto-generates keys and handles OAuth flows automatically
|
|
1322
|
+
- 🔌 **Pre-configured Providers**: Built-in support for Google, GitHub, and Azure
|
|
1323
|
+
- 🎯 **RFC Compliant**: Implements DCR (RFC 7591), PKCE, and OAuth 2.1
|
|
1324
|
+
- 🔑 **Optional JWKS**: Support for RS256/ES256 token verification (via optional `jose` dependency)
|
|
1325
|
+
|
|
1326
|
+
**Quick Start:**
|
|
1327
|
+
|
|
1328
|
+
```ts
|
|
1329
|
+
import { FastMCP } from "fastmcp";
|
|
1330
|
+
import { GoogleProvider } from "fastmcp/auth";
|
|
1331
|
+
|
|
1332
|
+
const authProxy = new GoogleProvider({
|
|
1333
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
1334
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
1335
|
+
baseUrl: "https://your-server.com",
|
|
1336
|
+
scopes: ["openid", "profile", "email"],
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
const server = new FastMCP({
|
|
1340
|
+
name: "My Server",
|
|
1341
|
+
oauth: {
|
|
1342
|
+
enabled: true,
|
|
1343
|
+
authorizationServer: authProxy.getAuthorizationServerMetadata(),
|
|
1344
|
+
proxy: authProxy, // Routes automatically registered!
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
**Documentation:**
|
|
1350
|
+
|
|
1351
|
+
- [OAuth Proxy Features](docs/oauth-proxy-features.md) - Complete feature list and capabilities
|
|
1352
|
+
- [OAuth Proxy Implementation Guide](docs/oauth-proxy-guide.md) - Setup and configuration
|
|
1353
|
+
- [Python vs TypeScript Comparison](docs/oauth-python-typescript.md) - Feature comparison
|
|
1354
|
+
|
|
1355
|
+
#### OAuth Discovery Endpoints
|
|
1356
|
+
|
|
1357
|
+
FastMCP also supports OAuth discovery endpoints for direct integration with OAuth providers, supporting both **MCP Specification 2025-03-26** and **MCP Specification 2025-06-18**. This provides standard discovery endpoints that comply with RFC 8414 (OAuth 2.0 Authorization Server Metadata) and RFC 9470 (OAuth 2.0 Protected Resource Metadata):
|
|
1317
1358
|
|
|
1318
1359
|
```ts
|
|
1319
1360
|
import { FastMCP, DiscoveryDocumentCache } from "fastmcp";
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { EventEmitter } from 'events';
|
|
|
9
9
|
import http from 'http';
|
|
10
10
|
import { StrictEventEmitter } from 'strict-event-emitter-types';
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
import { O as OAuthProxy } from './OAuthProxy-BOCkkAhO.js';
|
|
12
13
|
|
|
13
14
|
declare class DiscoveryDocumentCache {
|
|
14
15
|
#private;
|
|
@@ -493,6 +494,16 @@ type ServerOptions<T extends FastMCPSessionAuth> = {
|
|
|
493
494
|
*/
|
|
494
495
|
tlsClientCertificateBoundAccessTokens?: boolean;
|
|
495
496
|
};
|
|
497
|
+
/**
|
|
498
|
+
* OAuth Proxy instance for automatic OAuth flow handling.
|
|
499
|
+
* When provided, FastMCP will automatically register OAuth endpoints:
|
|
500
|
+
* - /oauth/register (DCR)
|
|
501
|
+
* - /oauth/authorize
|
|
502
|
+
* - /oauth/token
|
|
503
|
+
* - /oauth/callback
|
|
504
|
+
* - /oauth/consent
|
|
505
|
+
*/
|
|
506
|
+
proxy?: OAuthProxy;
|
|
496
507
|
};
|
|
497
508
|
ping?: {
|
|
498
509
|
/**
|
package/dist/FastMCP.js
CHANGED
|
@@ -1645,6 +1645,160 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1645
1645
|
return;
|
|
1646
1646
|
}
|
|
1647
1647
|
}
|
|
1648
|
+
const oauthProxy = oauthConfig?.proxy;
|
|
1649
|
+
if (oauthProxy && oauthConfig?.enabled) {
|
|
1650
|
+
const url = new URL(req.url || "", `http://${host}`);
|
|
1651
|
+
try {
|
|
1652
|
+
if (req.method === "POST" && url.pathname === "/oauth/register") {
|
|
1653
|
+
let body = "";
|
|
1654
|
+
req.on("data", (chunk) => body += chunk);
|
|
1655
|
+
req.on("end", async () => {
|
|
1656
|
+
try {
|
|
1657
|
+
const request = JSON.parse(body);
|
|
1658
|
+
const response = await oauthProxy.registerClient(request);
|
|
1659
|
+
res.writeHead(201, { "Content-Type": "application/json" }).end(JSON.stringify(response));
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
const statusCode = error.statusCode || 400;
|
|
1662
|
+
res.writeHead(statusCode, { "Content-Type": "application/json" }).end(
|
|
1663
|
+
JSON.stringify(
|
|
1664
|
+
error.toJSON?.() || {
|
|
1665
|
+
error: "invalid_request"
|
|
1666
|
+
}
|
|
1667
|
+
)
|
|
1668
|
+
);
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (req.method === "GET" && url.pathname === "/oauth/authorize") {
|
|
1674
|
+
try {
|
|
1675
|
+
const params = Object.fromEntries(url.searchParams.entries());
|
|
1676
|
+
const response = await oauthProxy.authorize(
|
|
1677
|
+
params
|
|
1678
|
+
);
|
|
1679
|
+
const location = response.headers.get("Location");
|
|
1680
|
+
if (location) {
|
|
1681
|
+
res.writeHead(response.status, { Location: location }).end();
|
|
1682
|
+
} else {
|
|
1683
|
+
const html = await response.text();
|
|
1684
|
+
res.writeHead(response.status, { "Content-Type": "text/html" }).end(html);
|
|
1685
|
+
}
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
1688
|
+
JSON.stringify(
|
|
1689
|
+
error.toJSON?.() || {
|
|
1690
|
+
error: "invalid_request"
|
|
1691
|
+
}
|
|
1692
|
+
)
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
if (req.method === "GET" && url.pathname === "/oauth/callback") {
|
|
1698
|
+
try {
|
|
1699
|
+
const mockRequest = new Request(`http://${host}${req.url}`);
|
|
1700
|
+
const response = await oauthProxy.handleCallback(mockRequest);
|
|
1701
|
+
const location = response.headers.get("Location");
|
|
1702
|
+
if (location) {
|
|
1703
|
+
res.writeHead(response.status, { Location: location }).end();
|
|
1704
|
+
} else {
|
|
1705
|
+
const text = await response.text();
|
|
1706
|
+
res.writeHead(response.status).end(text);
|
|
1707
|
+
}
|
|
1708
|
+
} catch (error) {
|
|
1709
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
1710
|
+
JSON.stringify(
|
|
1711
|
+
error.toJSON?.() || {
|
|
1712
|
+
error: "server_error"
|
|
1713
|
+
}
|
|
1714
|
+
)
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
if (req.method === "POST" && url.pathname === "/oauth/consent") {
|
|
1720
|
+
let body = "";
|
|
1721
|
+
req.on("data", (chunk) => body += chunk);
|
|
1722
|
+
req.on("end", async () => {
|
|
1723
|
+
try {
|
|
1724
|
+
const mockRequest = new Request(`http://${host}/oauth/consent`, {
|
|
1725
|
+
body,
|
|
1726
|
+
headers: {
|
|
1727
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1728
|
+
},
|
|
1729
|
+
method: "POST"
|
|
1730
|
+
});
|
|
1731
|
+
const response = await oauthProxy.handleConsent(mockRequest);
|
|
1732
|
+
const location = response.headers.get("Location");
|
|
1733
|
+
if (location) {
|
|
1734
|
+
res.writeHead(response.status, { Location: location }).end();
|
|
1735
|
+
} else {
|
|
1736
|
+
const text = await response.text();
|
|
1737
|
+
res.writeHead(response.status).end(text);
|
|
1738
|
+
}
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
1741
|
+
JSON.stringify(
|
|
1742
|
+
error.toJSON?.() || {
|
|
1743
|
+
error: "server_error"
|
|
1744
|
+
}
|
|
1745
|
+
)
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (req.method === "POST" && url.pathname === "/oauth/token") {
|
|
1752
|
+
let body = "";
|
|
1753
|
+
req.on("data", (chunk) => body += chunk);
|
|
1754
|
+
req.on("end", async () => {
|
|
1755
|
+
try {
|
|
1756
|
+
const params = new URLSearchParams(body);
|
|
1757
|
+
const grantType = params.get("grant_type");
|
|
1758
|
+
let response;
|
|
1759
|
+
if (grantType === "authorization_code") {
|
|
1760
|
+
response = await oauthProxy.exchangeAuthorizationCode({
|
|
1761
|
+
client_id: params.get("client_id") || "",
|
|
1762
|
+
client_secret: params.get("client_secret") || void 0,
|
|
1763
|
+
code: params.get("code") || "",
|
|
1764
|
+
code_verifier: params.get("code_verifier") || void 0,
|
|
1765
|
+
grant_type: "authorization_code",
|
|
1766
|
+
redirect_uri: params.get("redirect_uri") || ""
|
|
1767
|
+
});
|
|
1768
|
+
} else if (grantType === "refresh_token") {
|
|
1769
|
+
response = await oauthProxy.exchangeRefreshToken({
|
|
1770
|
+
client_id: params.get("client_id") || "",
|
|
1771
|
+
client_secret: params.get("client_secret") || void 0,
|
|
1772
|
+
grant_type: "refresh_token",
|
|
1773
|
+
refresh_token: params.get("refresh_token") || "",
|
|
1774
|
+
scope: params.get("scope") || void 0
|
|
1775
|
+
});
|
|
1776
|
+
} else {
|
|
1777
|
+
throw {
|
|
1778
|
+
statusCode: 400,
|
|
1779
|
+
toJSON: () => ({ error: "unsupported_grant_type" })
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(response));
|
|
1783
|
+
} catch (error) {
|
|
1784
|
+
const statusCode = error.statusCode || 400;
|
|
1785
|
+
res.writeHead(statusCode, { "Content-Type": "application/json" }).end(
|
|
1786
|
+
JSON.stringify(
|
|
1787
|
+
error.toJSON?.() || {
|
|
1788
|
+
error: "invalid_request"
|
|
1789
|
+
}
|
|
1790
|
+
)
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
this.#logger.error("[FastMCP error] OAuth Proxy endpoint error", error);
|
|
1798
|
+
res.writeHead(500).end();
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1648
1802
|
res.writeHead(404).end();
|
|
1649
1803
|
};
|
|
1650
1804
|
#parseRuntimeConfig(overrides) {
|