diff --git a/gradle.properties b/gradle.properties
index c97c988f51647e4f24ca18df5a54d66ee1f6cb5d..04ae04e2d10f92e45bc0d81598dffd799e627f7d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -11,7 +11,7 @@ micronautVersion = 3.2.3
 micronautRxVersion = 2.1.1
 nodePluginVersion = 3.0.1
 nodeVersion = 16.3.0
-nshmpLibVersion = 1.2.8
+nshmpLibVersion = 1.2.10
 nshmpWsUtilsVersion = 0.3.11
 openApiVersion = 4.0.0
 shadowVersion = 7.1.2
diff --git a/gradle/nshm.gradle b/gradle/nshm.gradle
index 5eecb87f60f13bc56d519bbdc4b64f494cd659df..0e1a39a17c9477faf6afe3985adf691099b5e3bc 100644
--- a/gradle/nshm.gradle
+++ b/gradle/nshm.gradle
@@ -43,7 +43,7 @@ clean.dependsOn cleanNshm
 task nshms() {
   dependsOn cleanNshm
   def yaml = new Yaml()
-  def nshmConfig = new Yaml().load(new File("nshms.yml").newInputStream())
+  def nshmConfig = new Yaml().load(new File("${projectDir}/nshms.yml").newInputStream())
 
   doLast {
     for (nshm in nshmConfig.nshms) {
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/LogicTreesController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/LogicTreesController.java
index 1c6b2396ffb5f88780752728a34d06fbdc555631..2f941ff14789f3fe25383dea32c0bb8b150c654b 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/source/LogicTreesController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/LogicTreesController.java
@@ -70,7 +70,7 @@ public class LogicTreesController {
       description = "Returns the logic tree of features for the supplied ID",
       operationId = "source-tree-features")
   @ApiResponse(
-      description = "NSHM source logic tree",
+      description = "NSHM source logic tree of features",
       responseCode = "200",
       content = @Content(
           schema = @Schema(implementation = TreeResponse.class)))
@@ -94,7 +94,7 @@ public class LogicTreesController {
       description = "Returns the logic tree of MFDs for the supplied ID",
       operationId = "source-tree-mfds")
   @ApiResponse(
-      description = "NSHM source logic tree",
+      description = "NSHM source logic tree of MFDs",
       responseCode = "200",
       content = @Content(
           schema = @Schema(implementation = TreeResponse.class)))
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/MfdsController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/MfdsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d87c87bb4dd583ed65f3528671a0d2511829333
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/MfdsController.java
@@ -0,0 +1,131 @@
+package gov.usgs.earthquake.nshmp.www.source;
+
+import java.util.Optional;
+
+import gov.usgs.earthquake.nshmp.model.SourceType;
+import gov.usgs.earthquake.nshmp.model.TectonicSetting;
+import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
+import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import gov.usgs.earthquake.nshmp.www.source.MfdsService.Metadata;
+import gov.usgs.earthquake.nshmp.www.source.MfdsService.RequestData;
+
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.PathVariable;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Inject;
+
+/**
+ * Micronaut web service controller for summed MFDs in the installed model.
+ * These endpoints return the magnitude frequency distributions (MFDs)
+ * associated with the different tectonic settings and source types present in
+ * the model. MFDs for requested tetonic settings and source types are scaled by
+ * their branch weights and then summed.
+ *
+ * <p>See src/main/resources/application.yml nshmp-haz.model-path for installed
+ * model.
+ *
+ * <p>To run the Micronaut jar file with a model: java -jar
+ * path/to/nshmp-haz.jar --model=<path/to/model>
+ *
+ * @author U.S. Geological Survey
+ */
+@Tag(
+    name = MfdsService.NAME,
+    description = "USGS NSHM source model MFD service")
+@Controller("/mfds")
+public class MfdsController {
+
+  @Inject
+  private NshmpMicronautServlet servlet;
+
+  @Operation(
+      summary = "Source model tectonic settings and source types",
+      description = "Returns the tectonic settings and source types in the model",
+      operationId = "source-mfd-metadata")
+  @ApiResponse(
+      description = "Source MFD metadata",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(
+              implementation = MetadataResponse.class)))
+  @Get
+  public HttpResponse<String> doGetMetadata(HttpRequest<?> http) {
+    try {
+      return LogicTreesService.getMetadata(http);
+    } catch (Exception e) {
+      return ServletUtil.error(
+          LogicTreesService.LOG, e,
+          LogicTreesService.NAME,
+          http.getUri().toString());
+    }
+  }
+
+  /**
+   * @param setting model tectonic setting
+   */
+  @Operation(
+      summary = "Get a source model MFD for a tectonic setting",
+      description = "Returns the total MFD for the supplied tectonic setting",
+      operationId = "source-setting-mfd")
+  @ApiResponse(
+      description = "NSHM source MFD",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(implementation = Response.class)))
+  @Get(uri = "/{setting}")
+  public HttpResponse<String> doGetMfdSettingGroup(
+      HttpRequest<?> http,
+      @PathVariable TectonicSetting setting) {
+    try {
+      return MfdsService.getMfdGroup(http, setting, Optional.empty());
+    } catch (Exception e) {
+      return ServletUtil.error(
+          LogicTreesService.LOG, e,
+          LogicTreesService.NAME,
+          http.getUri().toString());
+    }
+  }
+
+  /**
+   * @param setting model tectonic setting
+   * @param type model source type}
+   */
+  @Operation(
+      summary = "Get a source model MFD for a tectonic setting and source type",
+      description = "Returns the total MFD for the supplied tectonic setting and source type",
+      operationId = "source-setting-type-mfd")
+  @ApiResponse(
+      description = "NSHM source MFD",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(implementation = Response.class)))
+  @Get(uri = "/{setting}/{type}")
+  public HttpResponse<String> doGetMfdTypeGroup(
+      HttpRequest<?> http,
+      @PathVariable TectonicSetting setting,
+      @PathVariable SourceType type) {
+    try {
+      return MfdsService.getMfdGroup(http, setting, Optional.of(type));
+    } catch (Exception e) {
+      return ServletUtil.error(
+          LogicTreesService.LOG, e,
+          LogicTreesService.NAME,
+          http.getUri().toString());
+    }
+  }
+
+  // Swagger schema
+  private static class MetadataResponse extends ResponseBody<String, Metadata> {}
+
+  // Swagger schema
+  private static class Response extends ResponseBody<RequestData, Object> {}
+
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/MfdsService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/MfdsService.java
new file mode 100644
index 0000000000000000000000000000000000000000..4062fd449bc564e643924d10a62246d07410fe06
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/MfdsService.java
@@ -0,0 +1,107 @@
+package gov.usgs.earthquake.nshmp.www.source;
+
+import java.util.Optional;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gov.usgs.earthquake.nshmp.model.HazardModel;
+import gov.usgs.earthquake.nshmp.model.Models;
+import gov.usgs.earthquake.nshmp.model.SourceType;
+import gov.usgs.earthquake.nshmp.model.TectonicSetting;
+import gov.usgs.earthquake.nshmp.www.HazVersion;
+import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import gov.usgs.earthquake.nshmp.www.source.SourceService.SourceModel;
+
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import jakarta.inject.Singleton;
+
+/**
+ * Source model MFD handler {@link MfdsController}
+ *
+ * @author U.S. Geological Survey
+ */
+@Singleton
+public class MfdsService {
+
+  static final String NAME = "Model MFDs";
+  static final Logger LOG = LoggerFactory.getLogger(LogicTreesService.class);
+
+  public static HttpResponse<String> getMetadata(HttpRequest<?> request) {
+    var url = request.getUri().toString();
+    var metadata = new Metadata(ServletUtil.model());
+    var response = ResponseBody.usage()
+        .name(NAME)
+        .url(url)
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
+        .request(url)
+        .response(metadata)
+        .build();
+    return HttpResponse.ok(ServletUtil.GSON2.toJson(response));
+  }
+
+  public static HttpResponse<String> getMfdGroup(
+      HttpRequest<?> request,
+      TectonicSetting setting,
+      Optional<SourceType> type) {
+
+    var url = request.getUri().toString();
+    var mfd = Models.mfd(ServletUtil.model(), setting, type);
+    var requestData = new RequestData(setting, type.orElse(null));
+    var response = ResponseBody.success()
+        .name(NAME)
+        .url(url)
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
+        .request(requestData)
+        .response(mfd)
+        .build();
+    return HttpResponse.ok(ServletUtil.GSON2.toJson(response));
+  }
+
+  static class Metadata {
+    final SourceModel model;
+    final Set<TectonicSetting> settings;
+    final Set<SourceType> types;
+
+    Metadata(HazardModel model) {
+      this.model = new SourceModel(model);
+      this.settings = model.settings();
+      this.types = model.types();
+    }
+
+    public SourceModel getModel() {
+      return model;
+    }
+
+    public Set<TectonicSetting> getSettings() {
+      return settings;
+    }
+
+    public Set<SourceType> getTypes() {
+      return types;
+    }
+  }
+
+  static class RequestData {
+    final TectonicSetting setting;
+    final SourceType type;
+
+    RequestData(TectonicSetting setting, SourceType type) {
+      this.setting = setting;
+      this.type = type;
+    }
+
+    public TectonicSetting getSetting() {
+      return setting;
+    }
+
+    public SourceType getType() {
+      return type;
+    }
+  }
+
+}