Morphir.Mep 0.5.0-alpha.3

Prefix Reserved
This is a prerelease version of Morphir.Mep.
dotnet add package Morphir.Mep --version 0.5.0-alpha.3
                    
NuGet\Install-Package Morphir.Mep -Version 0.5.0-alpha.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Morphir.Mep" Version="0.5.0-alpha.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Morphir.Mep" Version="0.5.0-alpha.3" />
                    
Directory.Packages.props
<PackageReference Include="Morphir.Mep" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Morphir.Mep --version 0.5.0-alpha.3
                    
#r "nuget: Morphir.Mep, 0.5.0-alpha.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Morphir.Mep@0.5.0-alpha.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Morphir.Mep&version=0.5.0-alpha.3&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Morphir.Mep&version=0.5.0-alpha.3&prerelease
                    
Install as a Cake Tool

Morphir.Mep

One implementation of the Morphir Extension Protocol (MEP) 0.2.0-draft.1 for morphir-dotnet hosts and guests: JSON-RPC 2.0 messages, Content-Length framing, the session lifecycle and the model transport. Work is tracked by #512 and add-mep-core. The roadmap is the Morphir extension model PRD, step 1.

Layers

Layer File What it does
Wire types Wire.fs Records and unions of the pinned contract, with parsed values (ProtocolVersion, SemanticVersion, Sha256Digest, RequestId). MepJson is generated from this file at build time by Morphir.Myriad.SystemTextJson. There is no reflection serializer.
Model transport ModelTransport.fs Tuple selection, the host check of a selection, envelope creation and admission. Pure functions.
Message codec Rpc.fs Classifies a JSON-RPC message by its members (JsonRpcMessage), keeps params raw, and writes messages. This is the only hand-written JSON.
Dispatcher Dispatcher.fs DispatchAsync(bytes, ct) returns the reply bytes, or none for a notification. A method table maps method names to typed handlers; a gate refuses a method before its handler runs.
Framing Framing.fs Content-Length frames on a Stream: header blocks up to 8192 bytes, bodies up to 64 MiB, checked before the body is read. A refused frame ends the connection.
Connection Connection.fs Reads frames, dispatches inbound requests concurrently, matches inbound responses to the pending-request table, and sends and receives $/cancelRequest.
Session Session.fs The pure lifecycle rules (Lifecycle.admit) and version negotiation, shared by hosts and guests.
Channel Channel.fs How a host session reaches its guest: a framed stream pair, or ISingleMessageChannel for a guest that takes one message per call. SessionFailure says why a single-message session ended.
Peers Peers.fs Guest (lifecycle methods, gate, extension methods) and HostSession (refuses to send what the guest must refuse).

On the guest side, the stream path (Connection) and the single-message path (Dispatcher.DispatchAsync) use the same dispatcher, so they give the same bytes. On the host side, HostSession holds a channel and applies the same lifecycle, version and model-transport rules on either. Starting a guest process is not in this project: a process host connects a HostSession to the child's streams.

Channels

A host session sends a request and waits for its reply, and sends notifications. It does both through a channel (CHAN-1). HostSession checks each request against the lifecycle state before it uses the channel, so a request refused on one channel is refused on the other with the same error, and the guest never receives it.

Channel Constructor What the guest is
Framed stream pair HostSession(input, output, host, offer) A process with Content-Length frames on its standard streams. Requests run concurrently and are matched by id; a cancelled request sends $/cancelRequest; the guest can send morphir.progress.
Single message HostSession(channel, host, offer) A guest that takes one whole message per call, such as the handle export of a WASM guest. The runtime implements ISingleMessageChannel; tests/Morphir.Mep.AotProbe has a C# implementation.

ISingleMessageChannel has two members. Both take the method name, the bytes of one complete UTF-8 JSON-RPC message with no Content-Length header, and a cancellation token. Both return the bytes of the message the guest returned, or null when it returned none.

  • RequestAsync calls the guest with a request.
  • NotifyAsync calls the guest with a notification, or withholds it when the guest's runtime does not deliver notifications to that guest. A withheld notification does not call the guest. HostSession always sends morphir.initialized and morphir.exit; the runtime decides whether the guest receives them.

The implementation passes bytes and does not check them. HostSession applies these rules to a single-message guest:

  • One call at a time. A request waits until no other call is inside the guest.
  • A request takes exactly one reply: the response or error response with the request's id. No bytes, a response for another id, a request or notification (such as morphir.progress), bytes that are not a JSON-RPC message, or a reply over ContentLengthFraming.MaxBodyBytes fail the session.
  • A notification takes no reply. Bytes returned for one fail the session.
  • The host never sends $/cancelRequest. When a request's token is cancelled, the channel stops the call and ends it with OperationCanceledException. The request ends with -32800 and the session ends.
  • An exception from the channel fails the session.
  • After the session fails, no message reaches the guest. Failure (C#: FailureOrNull()) says why, as a SessionFailure. The request that failed gets -32800 when it was cancelled and -32603 otherwise. Later requests get -32603 without a call. A failure during InitializeAsync gives InitializeFailure.Refused with that error. RunAsync ends with Stopped at exit or failure.

Which notifications a WASM guest accepts is read from its morphir.initialize result by the runtime, not by HostSession. The member that advertises it is not fixed in the contract yet (NTFY-1).

A guest and a host in memory

This example is compiled by tests/Morphir.Mep.Tests/ReadmeTests.fs. Keep the two equal.

let runSession (claims: CapabilityClaimSet) (capabilities: ExtensionCapabilities) =
    task {
        let guest =
            Guest(
                { Claims = claims
                  Capabilities = capabilities
                  Methods = readOnlyDict [] }
            )

        let toGuest = Pipe()
        let toHost = Pipe()
        let guestRun = guest.RunAsync(toGuest.Reader.AsStream(), toHost.Writer.AsStream(), CancellationToken.None)

        let host =
            HostSession(
                toHost.Reader.AsStream(),
                toGuest.Writer.AsStream(),
                { Kind = PeerKind.Cli; Name = "morphir"; Version = "0.4.0" },
                null
            )

        let hostRun = host.RunAsync CancellationToken.None
        let! session = host.InitializeAsync CancellationToken.None
        let! ping = host.PingAsync CancellationToken.None
        let! _ = host.ShutdownAsync CancellationToken.None
        do! host.ExitAsync()
        let! guestEnd = guestRun
        toHost.Writer.Complete()
        let! _ = hostRun
        return session, ping, guestEnd
    }

Add a method

  1. Check that the pinned contract has the method and its params and result types. Do not add a method, member or error code that the pin does not have; raise an upstream issue.
  2. Write a failing wire test, then add the types to Wire.fs with the attributes that the contract's wire rules need. Add the type to the contract test (ContractTests.fs): a complete sample, and the members that the contract text lets a reader default.
  3. Add the method to Method if it is new, and its capability to Lifecycle.capability if the contract makes it depend on one (the guest replies -32013 without it).
  4. Register a handler in the guest's Methods with Handlers.request (params bind as one object through their codec; absent or mismatched params give -32602), and add a typed call to HostSession if hosts send it.
  5. Add the method to the AOT probe (tests/Morphir.Mep.AotProbe) when it changes what C# calls.

The pin

tests/Fixtures/Mep pins the upstream contract model (mep.ir.json), the transcripts and the model-transport corpus by commit and digest. Do not edit the fixtures. To update the pin:

  1. Copy the new files from finos/morphir at one commit, and record the commit and the SHA-256 digests in PROVENANCE.md.
  2. Run the tests. ContractTests fails for each renamed or added member, and the transcript and corpus tests fail for each changed behavior.
  3. Change the types and rules in the same pull request. A new MEP version moves every MEP part in morphir-dotnet together; drafts match only exactly.

Where a transcript and the contract text disagree, the tests follow the contract text. The legacy transcripts initialize with 0.1; the replays use 0.2.0-draft.1 instead.

C# callers

The project is trimmable and AOT-compatible, with F# nullness metadata. Parsed values have TryParse and Create; FrameRead and SessionFailure have Match. tests/Morphir.Mep.AotProbe is a C# consumer that runs the full session on streams and on a C# ISingleMessageChannel; InteropTests and ChannelTests compile C# examples and check the nullable-flow diagnostics.

Tests

dotnet run --project tests/Morphir.Mep.Tests -- --summary
./build.sh --target TestAot --configuration Release

The first command runs the wire, contract, JSON-RPC, connection, session, channel and model-transport tests in memory. The channel tests use a fake single-message guest built on Dispatcher.DispatchAsync. The second is the Native AOT lane of the black-box tests: it publishes the probe (and the other probes and library hosts) with Native AOT, refuses IL diagnostics other than the FSharp.Core summary baseline, and compares the native output with the managed output. Both run in PR CI.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on Morphir.Mep:

Package Downloads
Morphir.Extensions

Morphir extension registry domain: parsed extension identities, capability claims and the extension catalog

Morphir.Compilation.Mep

The MEP 0.2.0-draft.1 host side of guest processes: the guest process and session, and the frontend adapter, an IFrontend over a guest that serves morphir.frontend.compileModel. Trim-safe and AOT-safe.

Morphir.Extensions.Wasm

The WASM extension runtime of morphir-dotnet: Wasmtime with the embedded Extism kernel module.

Morphir.Extensions.Installation

Local extension package installation, inventory, selection and activation for Morphir tooling.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.5.0-alpha.3 57 11/14/2026
0.5.0-alpha.2 53 11/13/2026
0.5.0-alpha.1 55 11/13/2026
0.4.0-alpha.5 62 11/13/2026
0.4.0-alpha.4 65 11/10/2026
0.4.0-alpha.3 71 11/9/2026

### Added

- `morphir extension install nuget:<package-id>[@<version>] --home <abs-home> --no-probe` installs an extension from a NuGet feed: nuget.org, a private V3 feed such as Azure Artifacts, or a folder feed. It reads the sources, package source mapping, credentials (`packageSourceCredentials` and credential provider plugins) and signature policy of `nuget.config`, as `dotnet tool install` does, through the Microsoft NuGet client packages. `--source` names one source (a configured name, a V3 `index.json` URL or a folder), `--configfile` names the `nuget.config`, and `--interactive` lets a credential provider prompt; the three options apply only to a `nuget:` source. No version selects the highest stable listed version. The main package carries `morphir/release.json`; a process artifact is the subtree under its `root` of the main package (one package) or of a package whose id extends the main id (one package per platform, such as the RID packages of a self-contained .NET tool), pinned by its tree digest. A framework-dependent .NET tool is refused. Signatures are checked as the dotnet CLI checks them on each operating system. The command writes only to the Home: no NuGet HTTP cache, global packages folder or NuGet temporary folder. New codes: `extension.nuget_not_found`, `extension.nuget_unreachable`, `extension.nuget_unauthorized`, `extension.nuget_signature_invalid`. See [Publish an extension as a NuGet package](docs/content/docs/guides/publish-extension-nuget.md).
- `morphir extension install` installs WASM extensions from every provider: a local declaration, a GitHub release asset, or the entry `morphir/<filename>` of a NuGet main package. The module is checked against its descriptor `sha256` (`extension.digest_mismatch`) and stored in the single-file store; the lock records `runtime` `wasm` and the claims stay unchecked. The guest does not run during installation. `morphir compile` and `morphir generate` with `--extension-id` run an installed WASM extension through the host's WASM runtime, with the loader that configured modules use: they rehash the stored module against the lock first, and check the session against the installed claims. A host without a WASM runtime fails with `extension.runtime_unavailable`.
- On Linux and macOS, NuGet signature checks use the code signing and timestamping trust lists of the .NET SDK, which `Morphir.Extensions.Installation` carries, as the dotnet CLI does. Without them, NuGet on Linux has no trusted root, and a `require` policy refused every nuget.org package.
- `NuGetPackageProvider` in `Morphir.Extensions.Installation`, and `ExtensionSourceRequest.WithNuGetOptions`. `ExtensionSources.ProviderFor(request, http, home)` takes the Home for a NuGet source. The package ships `buildTransitive/Morphir.Extensions.Installation.props`, which sets the `NuGet.UseSystemTextJsonDeserialization` feature switch in each application that references it.
- `morphir extension install github:<owner>/<repo>[@<version-or-tag>] --home <abs-home> --no-probe` installs an extension package from a GitHub release ([#493](https://github.com/finos/morphir-dotnet/issues/493)). The release carries the extensions-family `release.json` as an asset; the command picks the artifact for this platform, streams the asset into a private stage and checks its SHA-256 against the descriptor before the installer reads it. `@<version>` selects the tag `v<version>`, else `<version>`; no `@` selects the latest release. It uses the anonymous GitHub API, or `GITHUB_TOKEN` or `GH_TOKEN` when set, and reports a rate limit as `extension.github_rate_limited`. `--local-developer` does not apply to a named release; `--no-probe` is still required. The installed extension then runs with `--extension-id`, with no network.
- Source providers in `Morphir.Extensions.Installation`: `IExtensionSourceProvider` (`Scheme`, `ResolveAsync`, `FetchAsync`), with `GitHubReleaseProvider` and `LocalRepositoryProvider`. A provider only resolves and fetches; validation, the package tree, and the catalog, lock and journal writes stay in one installer. A new source (a plain HTTPS URL, OCI, a registry index) is one more provider.

### Changed

- The extension installer writes and reads the extension family `0.1.0-preview.3` ([finos/morphir#1176](https://github.com/finos/morphir/pull/1176), merged as `65bbbc96`): the package format `nupkg` (a NuGet package, pinned by its tree digest only), the provenance `nuget-package` and the source `nuget-package-entry`. Catalogs, locks, journals, repositories and release descriptors of `0.1.0-preview.2` are refused and not converted: install again into a new Home, and publish `release.json` with the preview.3 `$schema`. A release descriptor may now offer `process` and `wasm` artifacts; the installer selects the `process` artifact for the platform, else a `wasm` artifact, and the lock records the selection.
- Build: the Native AOT publish of `morphir` fails on any trim or AOT warning from `NuGet.*` or `Newtonsoft.Json`. `morphir` and `dotnet-morphir` set `NuGet.UseSystemTextJsonDeserialization`, with which NuGet.Protocol has no such warning.
- The extension installer writes and reads the extension family `0.1.0-preview.2` ([finos/morphir#1173](https://github.com/finos/morphir/pull/1173)), where an artifact `source` and an `index` provenance are provider-tagged unions (`local-file` or `github-release-asset`, `local-directory` or `github-release`). Catalogs, locks, journals, repositories and release descriptors of `0.1.0-preview.1` are refused and not converted: install again into a new Home, and publish `release.json` with the preview.2 `$schema`. The package tree manifest format is unchanged.
- The install scripts `scripts/install-morphirup.sh` and `scripts/install-morphirup.ps1` install `morphirup` from the [finos/morphir releases](https://github.com/finos/morphir/releases): they download `morphirup` and `morphirup-v<version>.sha256` from `https://github.com/finos/morphir/releases/download/v<version>/`; the scripts stay hosted here. `morphirup` is on finos/morphir from `0.5.0-alpha.2` on; earlier `morphirup` releases stay on the [finos/morphir-dotnet releases](https://github.com/finos/morphir-dotnet/releases). With no version, the scripts take the newest finos/morphir release that has `morphirup` for the machine and skip the others, such as `v0.5.0-alpha.1` (the CLI only) and the releases of the earlier Rust CLI (`v0.4.0-beta.*` and older), in the release list (up to 100 releases from the GitHub API) and in the release feed `https://github.com/finos/morphir/releases.atom`. A given version whose release has no `morphirup` for the machine is refused with "finos/morphir release v<version> has no morphirup-<rid>; morphirup releases before 0.5.0-alpha.2 are on finos/morphir-dotnet": the `sh` script reads the HTTP status of the download instead of letting `curl -f` report a 404 as a network error, and the PowerShell script reads the status of the failed request. The line `downloading <file> <version> from <url>` names the download, and `MORPHIRUP_DOWNLOAD_URL` mirrors `https://github.com/finos/morphir/releases/download`. Two testing hooks, `MORPHIRUP_RELEASES_FILE` and `MORPHIRUP_FEED_FILE`, read the release list and the release feed from local files; the end-to-end scenarios of `InstallScripts.feature` use them with a local release mirror to check, with no network, that the script of the machine skips the releases without `morphirup` in both and refuses a Rust release given by version.
- mise installs `morphirup` with `mise use -g 'github:finos/morphir[matching=morphirup,version_prefix=v,prerelease=true]'`. mise also lists the releases without `morphirup`; with one of those it stops with "No matching asset found" and installs nothing, so name a release that has `morphirup`, such as `@0.5.0-alpha.2`.
- The proto plugin downloads the `morphir` CLI from `https://github.com/finos/morphir/releases/download/v<version>/`. The plugin itself is still released here.
- The manual workflows `install-scripts.yml` and `morphirup-release-journey.yml` use the finos/morphir releases. The pinned version of `install-scripts.yml` is `0.5.0-alpha.2`, each of its runs checks that the download comes from finos/morphir, and a run of the Rust release `0.4.0-beta.12` must be refused by name.