fastmcp 4.3.2 → 4.5.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 +62 -0
- package/dist/FastMCP.cjs +2 -2
- package/dist/FastMCP.d.cts +15 -6
- package/dist/FastMCP.d.ts +15 -6
- package/dist/FastMCP.js +1 -1
- package/dist/{chunk-UYG7NPM6.cjs → chunk-BJF2JTTE.cjs} +163 -88
- package/dist/chunk-BJF2JTTE.cjs.map +1 -0
- package/dist/{chunk-LWU5CQGW.js → chunk-BTMLQWIR.js} +134 -59
- package/dist/chunk-BTMLQWIR.js.map +1 -0
- package/dist/examples/custom-routes.cjs +2 -2
- package/dist/examples/custom-routes.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LWU5CQGW.js.map +0 -1
- package/dist/chunk-UYG7NPM6.cjs.map +0 -1
|
@@ -599,6 +599,67 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
599
599
|
});
|
|
600
600
|
});
|
|
601
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Builds the context object passed as the third argument to
|
|
604
|
+
* `resource.load` / `resourceTemplate.load` / `prompt.load`.
|
|
605
|
+
*
|
|
606
|
+
* This mirrors the `client`, `log`, `requestId`, `session`, and
|
|
607
|
+
* `sessionId` fields available to `tool.execute` via {@link Context}.
|
|
608
|
+
* `reportProgress` and `streamContent` are intentionally omitted: they
|
|
609
|
+
* are tied to a tool call's progress token / streaming notification,
|
|
610
|
+
* which resource and prompt reads do not have.
|
|
611
|
+
*/
|
|
612
|
+
#createLoadContext(meta) {
|
|
613
|
+
return {
|
|
614
|
+
client: {
|
|
615
|
+
version: this.#server.getClientVersion()
|
|
616
|
+
},
|
|
617
|
+
log: this.#createLog(),
|
|
618
|
+
requestId: typeof meta?.requestId === "string" ? meta.requestId : void 0,
|
|
619
|
+
session: this.#auth,
|
|
620
|
+
sessionId: this.#sessionId
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
#createLog() {
|
|
624
|
+
return {
|
|
625
|
+
debug: (message, context) => {
|
|
626
|
+
this.#server.sendLoggingMessage({
|
|
627
|
+
data: {
|
|
628
|
+
context,
|
|
629
|
+
message
|
|
630
|
+
},
|
|
631
|
+
level: "debug"
|
|
632
|
+
});
|
|
633
|
+
},
|
|
634
|
+
error: (message, context) => {
|
|
635
|
+
this.#server.sendLoggingMessage({
|
|
636
|
+
data: {
|
|
637
|
+
context,
|
|
638
|
+
message
|
|
639
|
+
},
|
|
640
|
+
level: "error"
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
info: (message, context) => {
|
|
644
|
+
this.#server.sendLoggingMessage({
|
|
645
|
+
data: {
|
|
646
|
+
context,
|
|
647
|
+
message
|
|
648
|
+
},
|
|
649
|
+
level: "info"
|
|
650
|
+
});
|
|
651
|
+
},
|
|
652
|
+
warn: (message, context) => {
|
|
653
|
+
this.#server.sendLoggingMessage({
|
|
654
|
+
data: {
|
|
655
|
+
context,
|
|
656
|
+
message
|
|
657
|
+
},
|
|
658
|
+
level: "warning"
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
602
663
|
#formatSchemaIssues(issues) {
|
|
603
664
|
return this.#utils?.formatInvalidParamsErrorMessage ? this.#utils.formatInvalidParamsErrorMessage(issues) : issues.map((issue) => {
|
|
604
665
|
const path = issue.path?.join(".") || "root";
|
|
@@ -812,7 +873,8 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
812
873
|
try {
|
|
813
874
|
result = await prompt.load(
|
|
814
875
|
args,
|
|
815
|
-
this.#auth
|
|
876
|
+
this.#auth,
|
|
877
|
+
this.#createLoadContext(request.params?._meta)
|
|
816
878
|
);
|
|
817
879
|
} catch (error) {
|
|
818
880
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -874,7 +936,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
874
936
|
continue;
|
|
875
937
|
}
|
|
876
938
|
const uri = uriTemplate.fill(match);
|
|
877
|
-
const result = await resourceTemplate.load(
|
|
939
|
+
const result = await resourceTemplate.load(
|
|
940
|
+
match,
|
|
941
|
+
this.#auth,
|
|
942
|
+
this.#createLoadContext(request.params?._meta)
|
|
943
|
+
);
|
|
878
944
|
const resources = Array.isArray(result) ? result : [result];
|
|
879
945
|
return {
|
|
880
946
|
contents: resources.map((resource2) => ({
|
|
@@ -896,7 +962,10 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
896
962
|
}
|
|
897
963
|
let maybeArrayResult;
|
|
898
964
|
try {
|
|
899
|
-
maybeArrayResult = await resource.load(
|
|
965
|
+
maybeArrayResult = await resource.load(
|
|
966
|
+
this.#auth,
|
|
967
|
+
this.#createLoadContext(request.params?._meta)
|
|
968
|
+
);
|
|
900
969
|
} catch (error) {
|
|
901
970
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
902
971
|
throw new McpError(
|
|
@@ -1065,44 +1134,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1065
1134
|
);
|
|
1066
1135
|
}
|
|
1067
1136
|
};
|
|
1068
|
-
const log =
|
|
1069
|
-
debug: (message, context) => {
|
|
1070
|
-
this.#server.sendLoggingMessage({
|
|
1071
|
-
data: {
|
|
1072
|
-
context,
|
|
1073
|
-
message
|
|
1074
|
-
},
|
|
1075
|
-
level: "debug"
|
|
1076
|
-
});
|
|
1077
|
-
},
|
|
1078
|
-
error: (message, context) => {
|
|
1079
|
-
this.#server.sendLoggingMessage({
|
|
1080
|
-
data: {
|
|
1081
|
-
context,
|
|
1082
|
-
message
|
|
1083
|
-
},
|
|
1084
|
-
level: "error"
|
|
1085
|
-
});
|
|
1086
|
-
},
|
|
1087
|
-
info: (message, context) => {
|
|
1088
|
-
this.#server.sendLoggingMessage({
|
|
1089
|
-
data: {
|
|
1090
|
-
context,
|
|
1091
|
-
message
|
|
1092
|
-
},
|
|
1093
|
-
level: "info"
|
|
1094
|
-
});
|
|
1095
|
-
},
|
|
1096
|
-
warn: (message, context) => {
|
|
1097
|
-
this.#server.sendLoggingMessage({
|
|
1098
|
-
data: {
|
|
1099
|
-
context,
|
|
1100
|
-
message
|
|
1101
|
-
},
|
|
1102
|
-
level: "warning"
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
};
|
|
1137
|
+
const log = this.#createLog();
|
|
1106
1138
|
const streamContent = async (content) => {
|
|
1107
1139
|
const contentArray = Array.isArray(content) ? content : [content];
|
|
1108
1140
|
try {
|
|
@@ -1230,6 +1262,20 @@ function convertObjectToSnakeCase(obj) {
|
|
|
1230
1262
|
}
|
|
1231
1263
|
return result;
|
|
1232
1264
|
}
|
|
1265
|
+
function joinPaths(basePath, path) {
|
|
1266
|
+
return `${basePath}${normalizePath(path)}`;
|
|
1267
|
+
}
|
|
1268
|
+
function normalizeBasePath(path) {
|
|
1269
|
+
if (!path || path === "/") {
|
|
1270
|
+
return "";
|
|
1271
|
+
}
|
|
1272
|
+
const withLeadingSlash = path.startsWith("/") ? path : `/${path}`;
|
|
1273
|
+
const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, "");
|
|
1274
|
+
return withoutTrailingSlash ? withoutTrailingSlash : "";
|
|
1275
|
+
}
|
|
1276
|
+
function normalizePath(path) {
|
|
1277
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
1278
|
+
}
|
|
1233
1279
|
function parseBasicAuthHeader(authHeader) {
|
|
1234
1280
|
const basicMatch = authHeader?.match(/^Basic\s+(.+)$/);
|
|
1235
1281
|
if (!basicMatch) return null;
|
|
@@ -1242,6 +1288,18 @@ function parseBasicAuthHeader(authHeader) {
|
|
|
1242
1288
|
return null;
|
|
1243
1289
|
}
|
|
1244
1290
|
}
|
|
1291
|
+
function stripBasePath(path, basePath) {
|
|
1292
|
+
if (!basePath) {
|
|
1293
|
+
return path;
|
|
1294
|
+
}
|
|
1295
|
+
if (path === basePath) {
|
|
1296
|
+
return "/";
|
|
1297
|
+
}
|
|
1298
|
+
if (path.startsWith(`${basePath}/`)) {
|
|
1299
|
+
return path.slice(basePath.length);
|
|
1300
|
+
}
|
|
1301
|
+
return null;
|
|
1302
|
+
}
|
|
1245
1303
|
var FastMCPEventEmitterBase = EventEmitter;
|
|
1246
1304
|
var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
|
|
1247
1305
|
};
|
|
@@ -1596,9 +1654,13 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1596
1654
|
} else if (config.transportType === "httpStream") {
|
|
1597
1655
|
const httpConfig = config.httpStream;
|
|
1598
1656
|
const protocol = httpConfig.sslCert || httpConfig.sslKey ? "https" : "http";
|
|
1657
|
+
const streamEndpoint = joinPaths(
|
|
1658
|
+
httpConfig.basePath,
|
|
1659
|
+
httpConfig.endpoint
|
|
1660
|
+
);
|
|
1599
1661
|
if (httpConfig.stateless) {
|
|
1600
1662
|
this.#logger.info(
|
|
1601
|
-
`[FastMCP info] Starting server in stateless mode on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${
|
|
1663
|
+
`[FastMCP info] Starting server in stateless mode on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${streamEndpoint}`
|
|
1602
1664
|
);
|
|
1603
1665
|
this.#httpStreamServer = await startHTTPServer({
|
|
1604
1666
|
...this.#authenticate ? { authenticate: this.#authenticate } : {},
|
|
@@ -1638,7 +1700,8 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1638
1700
|
res,
|
|
1639
1701
|
true,
|
|
1640
1702
|
httpConfig.host,
|
|
1641
|
-
|
|
1703
|
+
streamEndpoint,
|
|
1704
|
+
httpConfig.basePath
|
|
1642
1705
|
);
|
|
1643
1706
|
},
|
|
1644
1707
|
port: httpConfig.port,
|
|
@@ -1646,7 +1709,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1646
1709
|
sslCert: httpConfig.sslCert,
|
|
1647
1710
|
sslKey: httpConfig.sslKey,
|
|
1648
1711
|
stateless: true,
|
|
1649
|
-
streamEndpoint
|
|
1712
|
+
streamEndpoint
|
|
1650
1713
|
});
|
|
1651
1714
|
} else {
|
|
1652
1715
|
this.#httpStreamServer = await startHTTPServer({
|
|
@@ -1690,7 +1753,8 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1690
1753
|
res,
|
|
1691
1754
|
false,
|
|
1692
1755
|
httpConfig.host,
|
|
1693
|
-
|
|
1756
|
+
streamEndpoint,
|
|
1757
|
+
httpConfig.basePath
|
|
1694
1758
|
);
|
|
1695
1759
|
},
|
|
1696
1760
|
port: httpConfig.port,
|
|
@@ -1698,10 +1762,10 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1698
1762
|
sslCert: httpConfig.sslCert,
|
|
1699
1763
|
sslKey: httpConfig.sslKey,
|
|
1700
1764
|
stateless: httpConfig.stateless,
|
|
1701
|
-
streamEndpoint
|
|
1765
|
+
streamEndpoint
|
|
1702
1766
|
});
|
|
1703
1767
|
this.#logger.info(
|
|
1704
|
-
`[FastMCP info] server is running on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${
|
|
1768
|
+
`[FastMCP info] server is running on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${streamEndpoint}`
|
|
1705
1769
|
);
|
|
1706
1770
|
}
|
|
1707
1771
|
this.#serverState = "running" /* Running */;
|
|
@@ -1751,8 +1815,9 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1751
1815
|
/**
|
|
1752
1816
|
* Handles unhandled HTTP requests with health, readiness, OAuth endpoints, and custom routes
|
|
1753
1817
|
*/
|
|
1754
|
-
#handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint) => {
|
|
1818
|
+
#handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint, basePath = "") => {
|
|
1755
1819
|
const url = new URL(req.url || "", `http://${host}`);
|
|
1820
|
+
const basePathRelativePath = stripBasePath(url.pathname, basePath);
|
|
1756
1821
|
try {
|
|
1757
1822
|
const webRequest = this.#nodeRequestToWebRequest(req, url);
|
|
1758
1823
|
const honoResponse = await this.#honoApp.fetch(webRequest, {
|
|
@@ -1788,9 +1853,8 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1788
1853
|
const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
|
|
1789
1854
|
if (enabled) {
|
|
1790
1855
|
const path = healthConfig.path ?? "/health";
|
|
1791
|
-
const url2 = new URL(req.url || "", `http://${host}`);
|
|
1792
1856
|
try {
|
|
1793
|
-
if ((req.method === "GET" || req.method === "HEAD") &&
|
|
1857
|
+
if ((req.method === "GET" || req.method === "HEAD") && url.pathname === joinPaths(basePath, path)) {
|
|
1794
1858
|
res.writeHead(healthConfig.status ?? 200, {
|
|
1795
1859
|
"Content-Type": "text/plain"
|
|
1796
1860
|
}).end(
|
|
@@ -1798,7 +1862,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1798
1862
|
);
|
|
1799
1863
|
return;
|
|
1800
1864
|
}
|
|
1801
|
-
if ((req.method === "GET" || req.method === "HEAD") &&
|
|
1865
|
+
if ((req.method === "GET" || req.method === "HEAD") && url.pathname === joinPaths(basePath, "/ready")) {
|
|
1802
1866
|
if (isStateless) {
|
|
1803
1867
|
const response = {
|
|
1804
1868
|
mode: "stateless",
|
|
@@ -1837,7 +1901,11 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1837
1901
|
const oauthConfig = this.#options.oauth;
|
|
1838
1902
|
if (oauthConfig?.enabled && req.method === "GET") {
|
|
1839
1903
|
const url2 = new URL(req.url || "", `http://${host}`);
|
|
1840
|
-
|
|
1904
|
+
const authorizationServerMetadataPath = joinPaths(
|
|
1905
|
+
"",
|
|
1906
|
+
`/.well-known/oauth-authorization-server${basePath}`
|
|
1907
|
+
);
|
|
1908
|
+
if (url2.pathname === authorizationServerMetadataPath && oauthConfig.authorizationServer) {
|
|
1841
1909
|
const metadata = convertObjectToSnakeCase(
|
|
1842
1910
|
oauthConfig.authorizationServer
|
|
1843
1911
|
);
|
|
@@ -1868,8 +1936,9 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1868
1936
|
const oauthProxy = oauthConfig?.proxy;
|
|
1869
1937
|
if (oauthProxy && oauthConfig?.enabled) {
|
|
1870
1938
|
const url2 = new URL(req.url || "", `http://${host}`);
|
|
1939
|
+
const oauthPath = basePathRelativePath;
|
|
1871
1940
|
try {
|
|
1872
|
-
if (req.method === "POST" &&
|
|
1941
|
+
if (req.method === "POST" && oauthPath === "/oauth/register") {
|
|
1873
1942
|
await new Promise((resolve) => {
|
|
1874
1943
|
let body = "";
|
|
1875
1944
|
req.on("data", (chunk) => body += chunk);
|
|
@@ -1893,7 +1962,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1893
1962
|
});
|
|
1894
1963
|
return;
|
|
1895
1964
|
}
|
|
1896
|
-
if (req.method === "GET" &&
|
|
1965
|
+
if (req.method === "GET" && oauthPath === "/oauth/authorize") {
|
|
1897
1966
|
try {
|
|
1898
1967
|
const params = Object.fromEntries(url2.searchParams.entries());
|
|
1899
1968
|
const response = await oauthProxy.authorize(
|
|
@@ -1917,7 +1986,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1917
1986
|
}
|
|
1918
1987
|
return;
|
|
1919
1988
|
}
|
|
1920
|
-
if (req.method === "GET" &&
|
|
1989
|
+
if (req.method === "GET" && oauthPath === "/oauth/callback") {
|
|
1921
1990
|
try {
|
|
1922
1991
|
const mockRequest = new Request(`http://${host}${req.url}`);
|
|
1923
1992
|
const response = await oauthProxy.handleCallback(mockRequest);
|
|
@@ -1939,14 +2008,14 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1939
2008
|
}
|
|
1940
2009
|
return;
|
|
1941
2010
|
}
|
|
1942
|
-
if (req.method === "POST" &&
|
|
2011
|
+
if (req.method === "POST" && oauthPath === "/oauth/consent") {
|
|
1943
2012
|
await new Promise((resolve) => {
|
|
1944
2013
|
let body = "";
|
|
1945
2014
|
req.on("data", (chunk) => body += chunk);
|
|
1946
2015
|
req.on("end", async () => {
|
|
1947
2016
|
try {
|
|
1948
2017
|
const mockRequest = new Request(
|
|
1949
|
-
`http://${host}
|
|
2018
|
+
`http://${host}${url2.pathname}${url2.search}`,
|
|
1950
2019
|
{
|
|
1951
2020
|
body,
|
|
1952
2021
|
headers: {
|
|
@@ -1977,7 +2046,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1977
2046
|
});
|
|
1978
2047
|
return;
|
|
1979
2048
|
}
|
|
1980
|
-
if (req.method === "POST" &&
|
|
2049
|
+
if (req.method === "POST" && oauthPath === "/oauth/token") {
|
|
1981
2050
|
await new Promise((resolve) => {
|
|
1982
2051
|
let body = "";
|
|
1983
2052
|
req.on("data", (chunk) => body += chunk);
|
|
@@ -2081,11 +2150,13 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
2081
2150
|
const transportArg = getArg("transport");
|
|
2082
2151
|
const portArg = getArg("port");
|
|
2083
2152
|
const endpointArg = getArg("endpoint");
|
|
2153
|
+
const basePathArg = getArg("base-path");
|
|
2084
2154
|
const statelessArg = getArg("stateless");
|
|
2085
2155
|
const hostArg = getArg("host");
|
|
2086
2156
|
const envTransport = process.env.FASTMCP_TRANSPORT;
|
|
2087
2157
|
const envPort = process.env.FASTMCP_PORT;
|
|
2088
2158
|
const envEndpoint = process.env.FASTMCP_ENDPOINT;
|
|
2159
|
+
const envBasePath = process.env.FASTMCP_BASE_PATH;
|
|
2089
2160
|
const envStateless = process.env.FASTMCP_STATELESS;
|
|
2090
2161
|
const envHost = process.env.FASTMCP_HOST;
|
|
2091
2162
|
const transportType = overrides?.transportType || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
|
|
@@ -2095,6 +2166,9 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
2095
2166
|
);
|
|
2096
2167
|
const host = overrides?.httpStream?.host || hostArg || envHost || "localhost";
|
|
2097
2168
|
const endpoint = overrides?.httpStream?.endpoint || endpointArg || envEndpoint || "/mcp";
|
|
2169
|
+
const basePath = normalizeBasePath(
|
|
2170
|
+
overrides?.httpStream?.basePath || basePathArg || envBasePath
|
|
2171
|
+
);
|
|
2098
2172
|
const enableJsonResponse = overrides?.httpStream?.enableJsonResponse || false;
|
|
2099
2173
|
const stateless = overrides?.httpStream?.stateless || statelessArg === "true" || envStateless === "true" || false;
|
|
2100
2174
|
const cors = overrides?.httpStream?.cors;
|
|
@@ -2104,6 +2178,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
2104
2178
|
const sslKey = overrides?.httpStream?.sslKey;
|
|
2105
2179
|
return {
|
|
2106
2180
|
httpStream: {
|
|
2181
|
+
basePath,
|
|
2107
2182
|
cors,
|
|
2108
2183
|
enableJsonResponse,
|
|
2109
2184
|
endpoint,
|
|
@@ -2173,4 +2248,4 @@ export {
|
|
|
2173
2248
|
FastMCPSession,
|
|
2174
2249
|
FastMCP
|
|
2175
2250
|
};
|
|
2176
|
-
//# sourceMappingURL=chunk-
|
|
2251
|
+
//# sourceMappingURL=chunk-BTMLQWIR.js.map
|