@leanmcp/core 0.3.11 → 0.3.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.
package/dist/index.js CHANGED
@@ -41,6 +41,9 @@ function Tool(options = {}) {
41
41
  if (options.inputClass) {
42
42
  Reflect.defineMetadata("tool:inputClass", options.inputClass, descriptor.value);
43
43
  }
44
+ if (options.securitySchemes) {
45
+ Reflect.defineMetadata("tool:securitySchemes", options.securitySchemes, descriptor.value);
46
+ }
44
47
  };
45
48
  }
46
49
  function Prompt(options = {}) {
@@ -551,7 +554,8 @@ async function createHTTPServer(serverInput, options) {
551
554
  logging: serverOptions.logging,
552
555
  sessionTimeout: serverOptions.sessionTimeout,
553
556
  stateless: serverOptions.stateless,
554
- dashboard: serverOptions.dashboard
557
+ dashboard: serverOptions.dashboard,
558
+ auth: serverOptions.auth
555
559
  };
556
560
  }
557
561
  const [express, { StreamableHTTPServerTransport }, cors] = await Promise.all([
@@ -713,6 +717,73 @@ async function createHTTPServer(serverInput, options) {
713
717
  uptime: process.uptime()
714
718
  });
715
719
  });
720
+ app.get("/.well-known/oauth-protected-resource", (req, res) => {
721
+ const host = req.headers.host || "localhost";
722
+ const protocol = req.headers["x-forwarded-proto"] || "http";
723
+ const resource = httpOptions.auth?.resource || `${protocol}://${host}`;
724
+ const authServers = httpOptions.auth?.authorizationServers || [
725
+ resource
726
+ ];
727
+ res.json({
728
+ resource,
729
+ authorization_servers: authServers,
730
+ scopes_supported: httpOptions.auth?.scopesSupported || [],
731
+ resource_documentation: httpOptions.auth?.documentationUrl
732
+ });
733
+ });
734
+ if (httpOptions.auth?.enableOAuthServer && httpOptions.auth?.oauthServerOptions) {
735
+ const authOpts = httpOptions.auth.oauthServerOptions;
736
+ app.get("/.well-known/oauth-authorization-server", (req, res) => {
737
+ const host = req.headers.host || "localhost";
738
+ const protocol = req.headers["x-forwarded-proto"] || "http";
739
+ const issuer = httpOptions.auth?.resource || `${protocol}://${host}`;
740
+ res.json({
741
+ issuer,
742
+ authorization_endpoint: `${issuer}/oauth/authorize`,
743
+ token_endpoint: `${issuer}/oauth/token`,
744
+ registration_endpoint: `${issuer}/oauth/register`,
745
+ scopes_supported: httpOptions.auth?.scopesSupported || [],
746
+ response_types_supported: [
747
+ "code"
748
+ ],
749
+ grant_types_supported: [
750
+ "authorization_code",
751
+ "refresh_token"
752
+ ],
753
+ code_challenge_methods_supported: [
754
+ "S256"
755
+ ],
756
+ token_endpoint_auth_methods_supported: [
757
+ "client_secret_post",
758
+ "client_secret_basic",
759
+ "none"
760
+ ]
761
+ });
762
+ });
763
+ (async () => {
764
+ try {
765
+ const authServerModule = await import(
766
+ /* webpackIgnore: true */
767
+ "@leanmcp/auth/server"
768
+ );
769
+ const { OAuthAuthorizationServer } = authServerModule;
770
+ const authServer = new OAuthAuthorizationServer({
771
+ issuer: httpOptions.auth?.resource || `http://localhost:${basePort}`,
772
+ sessionSecret: authOpts.sessionSecret,
773
+ jwtSigningSecret: authOpts.jwtSigningSecret,
774
+ jwtEncryptionSecret: authOpts.jwtEncryptionSecret,
775
+ tokenTTL: authOpts.tokenTTL,
776
+ upstreamProvider: authOpts.upstreamProvider,
777
+ scopesSupported: httpOptions.auth?.scopesSupported,
778
+ enableDCR: true
779
+ });
780
+ app.use(authServer.getRouter());
781
+ logger.info("OAuth authorization server mounted");
782
+ } catch (e) {
783
+ logger.warn("OAuth server requested but @leanmcp/auth/server not available");
784
+ }
785
+ })();
786
+ }
716
787
  const handleMCPRequestStateful = /* @__PURE__ */ __name(async (req, res) => {
717
788
  const sessionId = req.headers["mcp-session-id"];
718
789
  let transport;
@@ -728,6 +799,17 @@ async function createHTTPServer(serverInput, options) {
728
799
  logMessage += ` (session: ${sessionId.substring(0, 8)}...)`;
729
800
  }
730
801
  logger.info(logMessage);
802
+ logger.info(logMessage);
803
+ if (req.headers.authorization) {
804
+ if (!req.body.params) req.body.params = {};
805
+ if (!req.body.params._meta) req.body.params._meta = {};
806
+ const authHeader = req.headers.authorization;
807
+ if (authHeader.startsWith("Bearer ")) {
808
+ req.body.params._meta.authToken = authHeader.substring(7);
809
+ } else {
810
+ req.body.params._meta.authToken = authHeader;
811
+ }
812
+ }
731
813
  try {
732
814
  if (sessionId && transports[sessionId]) {
733
815
  transport = transports[sessionId];
@@ -784,7 +866,18 @@ async function createHTTPServer(serverInput, options) {
784
866
  if (params?.name) logMessage += ` [${params.name}]`;
785
867
  else if (params?.uri) logMessage += ` [${params.uri}]`;
786
868
  logger.info(logMessage);
869
+ logger.info(logMessage);
787
870
  try {
871
+ if (req.headers.authorization) {
872
+ if (!req.body.params) req.body.params = {};
873
+ if (!req.body.params._meta) req.body.params._meta = {};
874
+ const authHeader = req.headers.authorization;
875
+ if (authHeader.startsWith("Bearer ")) {
876
+ req.body.params._meta.authToken = authHeader.substring(7);
877
+ } else {
878
+ req.body.params._meta.authToken = authHeader;
879
+ }
880
+ }
788
881
  const freshServer = await statelessServerFactory();
789
882
  if (freshServer && typeof freshServer.waitForInit === "function") {
790
883
  await freshServer.waitForInit();
@@ -941,6 +1034,56 @@ var init_http_server = __esm({
941
1034
  }
942
1035
  });
943
1036
 
1037
+ // src/auth-helpers.ts
1038
+ function createAuthError(message, options) {
1039
+ const error = options.error || "invalid_token";
1040
+ const errorDescription = options.errorDescription || message;
1041
+ const wwwAuth = `Bearer resource_metadata="${options.resourceMetadataUrl}", error="${error}", error_description="${errorDescription}"`;
1042
+ return {
1043
+ content: [
1044
+ {
1045
+ type: "text",
1046
+ text: message
1047
+ }
1048
+ ],
1049
+ _meta: {
1050
+ "mcp/www_authenticate": [
1051
+ wwwAuth
1052
+ ]
1053
+ },
1054
+ isError: true
1055
+ };
1056
+ }
1057
+ function isAuthError(result) {
1058
+ if (!result || typeof result !== "object") return false;
1059
+ const r = result;
1060
+ return r.isError === true && r._meta !== void 0 && typeof r._meta === "object" && r._meta !== null && "mcp/www_authenticate" in r._meta;
1061
+ }
1062
+ function extractBearerToken(authHeader) {
1063
+ if (!authHeader) return null;
1064
+ if (!authHeader.startsWith("Bearer ")) return null;
1065
+ return authHeader.slice(7);
1066
+ }
1067
+ function createProtectedResourceMetadata(options) {
1068
+ return {
1069
+ resource: options.resource,
1070
+ authorization_servers: options.authorizationServers || [
1071
+ options.resource
1072
+ ],
1073
+ scopes_supported: options.scopesSupported,
1074
+ resource_documentation: options.documentationUrl
1075
+ };
1076
+ }
1077
+ var init_auth_helpers = __esm({
1078
+ "src/auth-helpers.ts"() {
1079
+ "use strict";
1080
+ __name(createAuthError, "createAuthError");
1081
+ __name(isAuthError, "isAuthError");
1082
+ __name(extractBearerToken, "extractBearerToken");
1083
+ __name(createProtectedResourceMetadata, "createProtectedResourceMetadata");
1084
+ }
1085
+ });
1086
+
944
1087
  // src/index.ts
945
1088
  var index_exports = {};
946
1089
  __export(index_exports, {
@@ -960,10 +1103,14 @@ __export(index_exports, {
960
1103
  UserEnvs: () => UserEnvs,
961
1104
  classToJsonSchema: () => classToJsonSchema,
962
1105
  classToJsonSchemaWithConstraints: () => classToJsonSchemaWithConstraints,
1106
+ createAuthError: () => createAuthError,
963
1107
  createHTTPServer: () => createHTTPServer,
1108
+ createProtectedResourceMetadata: () => createProtectedResourceMetadata,
964
1109
  defaultLogger: () => defaultLogger,
1110
+ extractBearerToken: () => extractBearerToken,
965
1111
  getDecoratedMethods: () => getDecoratedMethods,
966
1112
  getMethodMetadata: () => getMethodMetadata,
1113
+ isAuthError: () => isAuthError,
967
1114
  startMCPServer: () => startMCPServer,
968
1115
  validateNonEmpty: () => validateNonEmpty,
969
1116
  validatePath: () => validatePath,
@@ -993,6 +1140,7 @@ var init_index = __esm({
993
1140
  init_http_server();
994
1141
  init_logger();
995
1142
  init_validation();
1143
+ init_auth_helpers();
996
1144
  init_decorators();
997
1145
  init_schema_generator();
998
1146
  init_logger();
@@ -1161,10 +1309,27 @@ var init_index = __esm({
1161
1309
  const meta = request.params._meta;
1162
1310
  const result = await tool.method.call(tool.instance, request.params.arguments, meta);
1163
1311
  let formattedResult = result;
1312
+ let structuredContent = void 0;
1164
1313
  if (methodMeta.renderFormat === "markdown" && typeof result === "string") {
1165
1314
  formattedResult = result;
1166
- } else if (methodMeta.renderFormat === "json" || typeof result === "object") {
1167
- formattedResult = JSON.stringify(result, null, 2);
1315
+ } else if (typeof result === "object" && result !== null) {
1316
+ if ("structuredContent" in result && Object.keys(result).length === 1) {
1317
+ structuredContent = result.structuredContent;
1318
+ formattedResult = JSON.stringify(structuredContent, null, 2);
1319
+ } else if ("content" in result && Array.isArray(result.content)) {
1320
+ const textItem = result.content.find((c) => c.type === "text");
1321
+ if (textItem?.text) {
1322
+ try {
1323
+ structuredContent = JSON.parse(textItem.text);
1324
+ } catch {
1325
+ structuredContent = textItem.text;
1326
+ }
1327
+ }
1328
+ formattedResult = JSON.stringify(result, null, 2);
1329
+ } else {
1330
+ structuredContent = result;
1331
+ formattedResult = JSON.stringify(result, null, 2);
1332
+ }
1168
1333
  } else {
1169
1334
  formattedResult = String(result);
1170
1335
  }
@@ -1176,6 +1341,12 @@ var init_index = __esm({
1176
1341
  }
1177
1342
  ]
1178
1343
  };
1344
+ if (structuredContent) {
1345
+ response.structuredContent = structuredContent;
1346
+ if (this.logger) {
1347
+ this.logger.debug(`[MCPServer] Setting structuredContent: ${JSON.stringify(structuredContent).slice(0, 100)}...`);
1348
+ }
1349
+ }
1179
1350
  if (tool._meta && Object.keys(tool._meta).length > 0) {
1180
1351
  response._meta = tool._meta;
1181
1352
  }
@@ -1987,10 +2158,14 @@ init_index();
1987
2158
  UserEnvs,
1988
2159
  classToJsonSchema,
1989
2160
  classToJsonSchemaWithConstraints,
2161
+ createAuthError,
1990
2162
  createHTTPServer,
2163
+ createProtectedResourceMetadata,
1991
2164
  defaultLogger,
2165
+ extractBearerToken,
1992
2166
  getDecoratedMethods,
1993
2167
  getMethodMetadata,
2168
+ isAuthError,
1994
2169
  startMCPServer,
1995
2170
  validateNonEmpty,
1996
2171
  validatePath,
package/dist/index.mjs CHANGED
@@ -22,6 +22,9 @@ function Tool(options = {}) {
22
22
  if (options.inputClass) {
23
23
  Reflect.defineMetadata("tool:inputClass", options.inputClass, descriptor.value);
24
24
  }
25
+ if (options.securitySchemes) {
26
+ Reflect.defineMetadata("tool:securitySchemes", options.securitySchemes, descriptor.value);
27
+ }
25
28
  };
26
29
  }
27
30
  __name(Tool, "Tool");
@@ -513,7 +516,8 @@ async function createHTTPServer(serverInput, options) {
513
516
  logging: serverOptions.logging,
514
517
  sessionTimeout: serverOptions.sessionTimeout,
515
518
  stateless: serverOptions.stateless,
516
- dashboard: serverOptions.dashboard
519
+ dashboard: serverOptions.dashboard,
520
+ auth: serverOptions.auth
517
521
  };
518
522
  }
519
523
  const [express, { StreamableHTTPServerTransport }, cors] = await Promise.all([
@@ -675,6 +679,73 @@ async function createHTTPServer(serverInput, options) {
675
679
  uptime: process.uptime()
676
680
  });
677
681
  });
682
+ app.get("/.well-known/oauth-protected-resource", (req, res) => {
683
+ const host = req.headers.host || "localhost";
684
+ const protocol = req.headers["x-forwarded-proto"] || "http";
685
+ const resource = httpOptions.auth?.resource || `${protocol}://${host}`;
686
+ const authServers = httpOptions.auth?.authorizationServers || [
687
+ resource
688
+ ];
689
+ res.json({
690
+ resource,
691
+ authorization_servers: authServers,
692
+ scopes_supported: httpOptions.auth?.scopesSupported || [],
693
+ resource_documentation: httpOptions.auth?.documentationUrl
694
+ });
695
+ });
696
+ if (httpOptions.auth?.enableOAuthServer && httpOptions.auth?.oauthServerOptions) {
697
+ const authOpts = httpOptions.auth.oauthServerOptions;
698
+ app.get("/.well-known/oauth-authorization-server", (req, res) => {
699
+ const host = req.headers.host || "localhost";
700
+ const protocol = req.headers["x-forwarded-proto"] || "http";
701
+ const issuer = httpOptions.auth?.resource || `${protocol}://${host}`;
702
+ res.json({
703
+ issuer,
704
+ authorization_endpoint: `${issuer}/oauth/authorize`,
705
+ token_endpoint: `${issuer}/oauth/token`,
706
+ registration_endpoint: `${issuer}/oauth/register`,
707
+ scopes_supported: httpOptions.auth?.scopesSupported || [],
708
+ response_types_supported: [
709
+ "code"
710
+ ],
711
+ grant_types_supported: [
712
+ "authorization_code",
713
+ "refresh_token"
714
+ ],
715
+ code_challenge_methods_supported: [
716
+ "S256"
717
+ ],
718
+ token_endpoint_auth_methods_supported: [
719
+ "client_secret_post",
720
+ "client_secret_basic",
721
+ "none"
722
+ ]
723
+ });
724
+ });
725
+ (async () => {
726
+ try {
727
+ const authServerModule = await import(
728
+ /* webpackIgnore: true */
729
+ "@leanmcp/auth/server"
730
+ );
731
+ const { OAuthAuthorizationServer } = authServerModule;
732
+ const authServer = new OAuthAuthorizationServer({
733
+ issuer: httpOptions.auth?.resource || `http://localhost:${basePort}`,
734
+ sessionSecret: authOpts.sessionSecret,
735
+ jwtSigningSecret: authOpts.jwtSigningSecret,
736
+ jwtEncryptionSecret: authOpts.jwtEncryptionSecret,
737
+ tokenTTL: authOpts.tokenTTL,
738
+ upstreamProvider: authOpts.upstreamProvider,
739
+ scopesSupported: httpOptions.auth?.scopesSupported,
740
+ enableDCR: true
741
+ });
742
+ app.use(authServer.getRouter());
743
+ logger.info("OAuth authorization server mounted");
744
+ } catch (e) {
745
+ logger.warn("OAuth server requested but @leanmcp/auth/server not available");
746
+ }
747
+ })();
748
+ }
678
749
  const handleMCPRequestStateful = /* @__PURE__ */ __name(async (req, res) => {
679
750
  const sessionId = req.headers["mcp-session-id"];
680
751
  let transport;
@@ -690,6 +761,17 @@ async function createHTTPServer(serverInput, options) {
690
761
  logMessage += ` (session: ${sessionId.substring(0, 8)}...)`;
691
762
  }
692
763
  logger.info(logMessage);
764
+ logger.info(logMessage);
765
+ if (req.headers.authorization) {
766
+ if (!req.body.params) req.body.params = {};
767
+ if (!req.body.params._meta) req.body.params._meta = {};
768
+ const authHeader = req.headers.authorization;
769
+ if (authHeader.startsWith("Bearer ")) {
770
+ req.body.params._meta.authToken = authHeader.substring(7);
771
+ } else {
772
+ req.body.params._meta.authToken = authHeader;
773
+ }
774
+ }
693
775
  try {
694
776
  if (sessionId && transports[sessionId]) {
695
777
  transport = transports[sessionId];
@@ -746,7 +828,18 @@ async function createHTTPServer(serverInput, options) {
746
828
  if (params?.name) logMessage += ` [${params.name}]`;
747
829
  else if (params?.uri) logMessage += ` [${params.uri}]`;
748
830
  logger.info(logMessage);
831
+ logger.info(logMessage);
749
832
  try {
833
+ if (req.headers.authorization) {
834
+ if (!req.body.params) req.body.params = {};
835
+ if (!req.body.params._meta) req.body.params._meta = {};
836
+ const authHeader = req.headers.authorization;
837
+ if (authHeader.startsWith("Bearer ")) {
838
+ req.body.params._meta.authToken = authHeader.substring(7);
839
+ } else {
840
+ req.body.params._meta.authToken = authHeader;
841
+ }
842
+ }
750
843
  const freshServer = await statelessServerFactory();
751
844
  if (freshServer && typeof freshServer.waitForInit === "function") {
752
845
  await freshServer.waitForInit();
@@ -892,6 +985,51 @@ async function createHTTPServer(serverInput, options) {
892
985
  }
893
986
  __name(createHTTPServer, "createHTTPServer");
894
987
 
988
+ // src/auth-helpers.ts
989
+ function createAuthError(message, options) {
990
+ const error = options.error || "invalid_token";
991
+ const errorDescription = options.errorDescription || message;
992
+ const wwwAuth = `Bearer resource_metadata="${options.resourceMetadataUrl}", error="${error}", error_description="${errorDescription}"`;
993
+ return {
994
+ content: [
995
+ {
996
+ type: "text",
997
+ text: message
998
+ }
999
+ ],
1000
+ _meta: {
1001
+ "mcp/www_authenticate": [
1002
+ wwwAuth
1003
+ ]
1004
+ },
1005
+ isError: true
1006
+ };
1007
+ }
1008
+ __name(createAuthError, "createAuthError");
1009
+ function isAuthError(result) {
1010
+ if (!result || typeof result !== "object") return false;
1011
+ const r = result;
1012
+ return r.isError === true && r._meta !== void 0 && typeof r._meta === "object" && r._meta !== null && "mcp/www_authenticate" in r._meta;
1013
+ }
1014
+ __name(isAuthError, "isAuthError");
1015
+ function extractBearerToken(authHeader) {
1016
+ if (!authHeader) return null;
1017
+ if (!authHeader.startsWith("Bearer ")) return null;
1018
+ return authHeader.slice(7);
1019
+ }
1020
+ __name(extractBearerToken, "extractBearerToken");
1021
+ function createProtectedResourceMetadata(options) {
1022
+ return {
1023
+ resource: options.resource,
1024
+ authorization_servers: options.authorizationServers || [
1025
+ options.resource
1026
+ ],
1027
+ scopes_supported: options.scopesSupported,
1028
+ resource_documentation: options.documentationUrl
1029
+ };
1030
+ }
1031
+ __name(createProtectedResourceMetadata, "createProtectedResourceMetadata");
1032
+
895
1033
  // src/index.ts
896
1034
  var ajv = new Ajv();
897
1035
  var MCPServer = class {
@@ -1058,10 +1196,27 @@ var MCPServer = class {
1058
1196
  const meta = request.params._meta;
1059
1197
  const result = await tool.method.call(tool.instance, request.params.arguments, meta);
1060
1198
  let formattedResult = result;
1199
+ let structuredContent = void 0;
1061
1200
  if (methodMeta.renderFormat === "markdown" && typeof result === "string") {
1062
1201
  formattedResult = result;
1063
- } else if (methodMeta.renderFormat === "json" || typeof result === "object") {
1064
- formattedResult = JSON.stringify(result, null, 2);
1202
+ } else if (typeof result === "object" && result !== null) {
1203
+ if ("structuredContent" in result && Object.keys(result).length === 1) {
1204
+ structuredContent = result.structuredContent;
1205
+ formattedResult = JSON.stringify(structuredContent, null, 2);
1206
+ } else if ("content" in result && Array.isArray(result.content)) {
1207
+ const textItem = result.content.find((c) => c.type === "text");
1208
+ if (textItem?.text) {
1209
+ try {
1210
+ structuredContent = JSON.parse(textItem.text);
1211
+ } catch {
1212
+ structuredContent = textItem.text;
1213
+ }
1214
+ }
1215
+ formattedResult = JSON.stringify(result, null, 2);
1216
+ } else {
1217
+ structuredContent = result;
1218
+ formattedResult = JSON.stringify(result, null, 2);
1219
+ }
1065
1220
  } else {
1066
1221
  formattedResult = String(result);
1067
1222
  }
@@ -1073,6 +1228,12 @@ var MCPServer = class {
1073
1228
  }
1074
1229
  ]
1075
1230
  };
1231
+ if (structuredContent) {
1232
+ response.structuredContent = structuredContent;
1233
+ if (this.logger) {
1234
+ this.logger.debug(`[MCPServer] Setting structuredContent: ${JSON.stringify(structuredContent).slice(0, 100)}...`);
1235
+ }
1236
+ }
1076
1237
  if (tool._meta && Object.keys(tool._meta).length > 0) {
1077
1238
  response._meta = tool._meta;
1078
1239
  }
@@ -1885,10 +2046,14 @@ export {
1885
2046
  UserEnvs,
1886
2047
  classToJsonSchema,
1887
2048
  classToJsonSchemaWithConstraints,
2049
+ createAuthError,
1888
2050
  createHTTPServer,
2051
+ createProtectedResourceMetadata,
1889
2052
  defaultLogger,
2053
+ extractBearerToken,
1890
2054
  getDecoratedMethods,
1891
2055
  getMethodMetadata,
2056
+ isAuthError,
1892
2057
  startMCPServer,
1893
2058
  validateNonEmpty,
1894
2059
  validatePath,