Hands-on tutorial

Get started with Foundgine

Run the Foundgine.SupplyChain sample end to end, then walk through it layer by layer — MCP boundary, application use cases, domain and AOT metadata, semantic model, and PostgreSQL execution. This page follows GUIDE.md in the sample.

What you need.NET 9 SDK, Docker, and the Foundgine repository checked out locally.
What you'll runThe layered Supply Chain MCP sample against a real PostgreSQL database.
What you'll understandWhy each project exists and what it is — and is not — allowed to reference.

What you'll run

The sample is a small supply-chain domain exposed to agents over MCP: customers, orders, order lines, products, suppliers, categories, inventory positions, warehouses, shipments and carriers. It is wired as a stack of single-purpose projects rather than one project with everything in it:

Source: samples/Foundgine.SupplyChain in the repository. It is deliberately separate from benchmarks/AgentEndToEnd/SupplyChain, which stays fixed as a benchmark workload while this sample is free to evolve as the recommended reference architecture.

1. Prerequisites

The sample under samples/Foundgine.SupplyChain references the Foundgine src/ projects directly (rather than the published NuGet packages), so it always builds against the exact source in the repository, including the current AOT source generator. If you copy the sample outside the repository, switch those ProjectReference entries to the published Foundgine.* NuGet packages instead — see PACKAGE-COMPATIBILITY.md in the sample folder.

2. Start PostgreSQL

The sample ships a docker-compose.yml that starts PostgreSQL and the API together:

cd samples/Foundgine.SupplyChain
docker compose up --build

This starts PostgreSQL on localhost:4429 and the API container on localhost:4422, with SupplyChainConnectionString already wired to the containerized database. If you'd rather run the API directly on the host and only containerize the database, start just the postgres service and point SupplyChainConnectionString at it yourself.

3. Run the API

From the repository root, with PostgreSQL reachable and SupplyChainConnectionString set:

dotnet run --project samples/Foundgine.SupplyChain/Api/Foundgine.SupplyChain.Api.csproj

Check that it's up:

curl http://localhost:4422/health
curl http://localhost:4422/health/ready   # confirms the database connection

The MCP endpoint is http://localhost:4422/mcp. Point an MCP client at it and you'll see the tools Api/Program.cs exposes — describe_capabilities, get_my_orders, get_order, get_shipment, list_products, list_customers, get_product, get_inventory, list_suppliers, update_inventory, create_shipment, update_shipment, place_order and cancel_order — every one a thin adapter over SupplyChainApplication, never a place that touches SQL directly.

4. Walk the architecture layer by layer

With the sample running, this is the order to read the code in — each layer only talks to the one directly below it.

1
Layer 1

API layer — Api

samples/Foundgine.SupplyChain/Api

Api/Program.cs is deliberately small: it creates the ASP.NET host, registers the application/infrastructure composition roots, enables the Foundgine MCP adapter, and maps /mcp and health endpoints. It contains no SQL, business rules, semantic definitions or authorization policy.

MCP request
    ↓
SupplyChainMcpTools
    ↓
SupplyChainApplication
2
Layer 2

Application layer — Application

samples/Foundgine.SupplyChain/Application

Defines the use-case boundary through ISupplyChainQueries and ISupplyChainMutations. SupplyChainApplication performs capability authorization before delegating to the appropriate port.

protocol
   ↓
application capability
   ↓
use-case contract
   ↓
provider implementation

Swapping MCP for another transport does not require changing the use cases.

3
Layer 3

Domain layer — Domain

samples/Foundgine.SupplyChain/Domain

Contains two intentionally different, unrelated CLR representations of the same business concepts:

Storage records*ERP types (CustomerERP, SalesOrderERP, CatalogProductERP, InventoryPositionERP...) decorated with FoundgineEntity/FoundgineField/FoundgineRelationship, carrying both semantic and physical names — e.g. SalesOrder stores as table orders, and SalesOrder.Id stores as column order_id.

Application modelsCustomer, SalesOrder, SalesOrderLine, CatalogProduct, InventoryPosition and friends, named for the business vocabulary rather than the database. The model type does not inherit from or reference the ERP type; the only link is the explicit FoundgineConnection declaration. This keeps the semantic vocabulary stable if the physical schema changes.

4
Layer 4

AOT layer — generated metadata

Foundgine.Aot + Foundgine.Aot.Generator

Foundgine.Aot attributes on the Domain types are compiled by Foundgine.Aot.Generator, which emits Foundgine.Generated.GeneratedMetadata. The sample consumes the generated registry through SupplyChainSemanticModel.Metadata.

AOT declarations
      ↓
Foundgine.Aot.Generator
      ↓
GeneratedMetadata
      ↓
IMetadataProvider
      ↓
Planner / SqlCompiler

The important architectural point: the runtime never rediscovers the storage metadata graph at run time — it's generated at compile time.

5
Layer 5

Semantic layer — Semantics

samples/Foundgine.SupplyChain/Semantics

SupplyChainSemanticModel holds stable semantic IDs for entities and relationships — CatalogProduct, InventoryPosition, SalesOrder, SalesOrderLine — used by semantic operations instead of raw database table names such as products, inventory, orders and order_items. This is the boundary that keeps the sample future-proof against schema changes.

6
Layer 6

Query repository — Infrastructure/Queries

samples/Foundgine.SupplyChain/Infrastructure/Queries

A query repository builds a semantic operation rather than a SQL string. For example, GetMyOrders follows:

GetOrders(customerId)
       ↓
SemanticReadNode(SalesOrder)
       ↓
SupplyChainSemanticFields.SalesOrder.CustomerId.Eq(customerId)
       ↓
Foundgine Planner
       ↓
Execution plan
       ↓
Foundgine.Sql.SqlCompiler
       ↓
SqlPlan
       ↓
SqlExecutionProvider
       ↓
PostgreSQL

There is no repository-level SQL string for the normal query path — if PostgreSQL is replaced later, this application code doesn't change.

7
Layer 7

Mutation repository — Infrastructure/Mutations

samples/Foundgine.SupplyChain/Infrastructure/Mutations

Simple mutations follow the same semantic path. update_inventory becomes:

SemanticMutationBuilder.Update
       ↓
MutationPlanner
       ↓
SqlMutationCompiler
       ↓
SqlMutationExecutionProvider
       ↓
PostgreSQL

This is the preferred path for any future mutation expressible in Foundgine's mutation IR.

8
Layer 8

High-assurance mutations

place_order / cancel_order

place_order and cancel_order are intentionally different: they carry invariants that are currently PostgreSQL-specific — idempotency/replay protection, advisory transaction locking, FOR UPDATE SKIP LOCKED, inventory reservation races, atomic order + allocation + inventory changes, and cancellation inventory restoration. The sample keeps explicit parameterized SQL here rather than pretending a generic repository abstraction makes those invariants disappear. The long-term direction is to progressively move expressible portions into Foundgine's mutation IR while keeping provider-specific transactional primitives where they're genuinely required.

9
Layer 9

MCP layer

Api/Program.cs — SupplyChainMcpTools

SupplyChainMcpTools contains only protocol adapters. A tool like get_order doesn't know how an order is stored or queried — it invokes the application capability directly. That keeps the MCP surface replaceable and the semantic application architecture transport-independent.

10
Layer 10

Testing layer — Tests

samples/Foundgine.SupplyChain/Tests

The seam for validating each layer independently. Recommended progression: capability authorization tests, AOT metadata tests, semantic plan tests, SQL compilation tests, PostgreSQL integration tests, MCP contract tests, then full agent-facing E2E benchmark tests. The existing benchmarks/AgentEndToEnd/SupplyChain stays untouched and continues to provide the benchmark workload and comparison harness.

The key dependency rule: API → Application → Semantic intent → Foundgine planning → Provider — never API → SQL repository → PostgreSQL directly. That's what lets another MCP transport, another database provider, a different SQL dialect, richer authorization, or additional agent-facing operations get introduced at the right boundary instead of forcing a rewrite of the application.

5. Next steps