Screenshot-ready C# samples for three Graftcode creatives. The code follows the public docs at https://docs.graftcode.com. It does not publish a package, install a generated Graft, or state a throughput or latency comparison.
Projects target net9.0. global.json uses rollForward: latestMajor, so the .NET 10 SDK can build them.
These tests do not need Docker and do not start Graftcode Gateway.
dotnet test GraftcodeSamples.slnOne example at a time:
dotnet test example-1-mcp-class/Example1.Tests
dotnet test example-2-before-after/Example2.Tests
dotnet test example-3-contracts/Example3.TestsWhat they do:
- Compile every sample project in the solution, including the example-3 hosts
rest-host/andgrpc-host/. The unit run does not start them. - Check method results, the illustrative HTTP client against a stub handler, and the gRPC adapter against the service base
Grpc.Toolsgenerates during the build. - Check that the sample files contain the documented flags, the MCP URL, the illustrative-REST marker, and the Caller wiring.
- Reject copy this repo will not show: a callable-surface filter described as authentication, and any throughput or latency comparison.
What they do not do:
- They do not execute
example-1-mcp-class/host/gg.sh. - They do not build the Dockerfiles.
- They do not open
http://localhost:81/mcpor install a generated Graft. GraftConfigandBillingServiceunderexample-2-before-after/after-callerare a local stand-in so the call site compiles. The stand-in calls the Receiver in-process. It does not open a socket. Aws://orwss://host on a real installed Graft is still a network call.
The e2e project has two kinds of tests:
- Gateway tests (examples 1, 2, and the Graftcode path of example 3) build a Dockerfile, run Gateway on ports
80and81, and callhttp://localhost:81/mcp. - Example 3 REST and gRPC tests start a real Kestrel server in the test process on
127.0.0.1with an ephemeral port (http://127.0.0.1:0) and call it over TCP. They do not need Docker and never bind80or81.
Requirements for the Gateway tests:
- Docker, with a running daemon.
- Network access during
docker build. Graftcode does not publish a Gateway image todocker pull(Deploy Gateway with Docker, Run Gateway locally). Each Dockerfile starts frommcr.microsoft.com/dotnet/sdk:9.0and installsggwith the .NET MCP quick start command:curl -fsSL grft.dev/get/gg | sh && dpkg -i gg.deb. - Host ports
80and81free. The quick start maps-p 80:80 -p 81:81. Port80is the WebSocket runtime port. Port81is the Vision HTTP port. MCP ishttp://localhost:81/mcp. Liveness isGET http://localhost:81/status(bodyOK). Vision ishttp://localhost:81/GV.
dotnet test e2e/Graftcode.E2e.Tests/Graftcode.E2e.Tests.csprojThe e2e project is not in GraftcodeSamples.sln, so the unit command above does not build images. If docker info fails, each Gateway test is skipped instead of failing. The REST and gRPC tests still run.
If another container already publishes 80 or 81 (for example a manually started graftcode-sample-invoice-calculator), stop it first with docker stop <name>; the suite only removes the containers it names itself.
What the e2e starts:
- Example 1 builds
example-1-mcp-class/host/Dockerfileand callshttp://localhost:81/mcp.tools/listmust includeCalculateMonthlyBillandApplyTax, and must not includeLateFeeorFormatInvoice.tools/calluses the argument names from that tool'sinputSchema(the Java MCP quick start says not to assume source parameter names). NoAuthorizationheader is sent. The filter is not authorization. Amounts on the hosted methods aredoubleand quantities areint, so a JSON number can bind on Gateway 1.4.14. The e2e tests require a numeric result. - Example 2 builds only
example-2-before-after/after-receiver/Dockerfileand callsCalculateMonthlyBillthrough that Gateway. The illustrative REST project is not started.LocalGraftStandInis still not a generated Graft and still does not open a socket. The hosted call is a real Gateway call. Aws://orwss://Graft remains a network call. The Caller source still has no_billingfield. - Example 3 exercises each of its three paths over the network, each on its own server:
- REST:
example-3-contracts/rest-host(RestHost.Build) servesBillingControllerfromrest/on Kestrel athttp://127.0.0.1:<ephemeral>. The test sendsPOST /api/v1/billing/monthly-billwith JSON{ "unitPrice": ..., "units": ... }and reads{ "total": ... }as a decimal:10and5give50,19.99and3give59.97. It also checks that/mcpis a 404 on that host. No stub handler and no in-process controller call. - gRPC:
example-3-contracts/grpc-host(GrpcHost.Build) mapsBillingGrpcServicefromgrpc/on Kestrel, plaintext HTTP/2, athttp://127.0.0.1:<ephemeral>. The test calls the generatedBillingApi.BillingApiClientthrough aGrpcChannel.unit_priceandtotalare proto strings:"10"and5give50,"19.99"and3give59.97, andunit_price = "nope"returns statusInvalidArgumentover the wire. - Graftcode: builds only
example-3-contracts/graftcode/Dockerfileand callsCalculateMonthlyBillthrough Gateway athttp://localhost:81/mcp.10and5must return the number50, and19.99and3must return59.97. The hosted method takesdoubleandint, so the JSON numbers bind on Gateway 1.4.14. - The REST and gRPC hosts are not MCP servers and are not started inside the Gateway image. Only the Graftcode path goes through
/mcp.
- REST:
What the e2e does not do:
- It does not pass
--projectKeyorGC_PROJECT_KEY. - It does not install a Graft and does not write down a package name, version, or registry URL. Copy those from Vision for the process you are looking at. The CLI default
--endpointishttps://grft.dev; this suite does not override it. - It does not run ads.
- Local image tags such as
graftcode-sample-invoice-calculator:e2eare tags this suite passes todocker build. They are not images Graftcode publishes.
The three Gateway tests share ports 80 and 81, so the project disables parallel test execution.
Run the example-3 hosts by hand:
# REST on http://127.0.0.1:5080
dotnet run --project example-3-contracts/rest-host -- http://127.0.0.1:5080
curl -s -X POST http://127.0.0.1:5080/api/v1/billing/monthly-bill \
-H "Content-Type: application/json" -d '{"unitPrice":19.99,"units":3}'
# gRPC (plaintext HTTP/2) on http://127.0.0.1:5081; grpcurl reads the .proto, no reflection service
dotnet run --project example-3-contracts/grpc-host -- http://127.0.0.1:5081
grpcurl -plaintext -import-path example-3-contracts/grpc -proto billing.proto \
-d '{"unit_price":"19.99","units":3}' 127.0.0.1:5081 billing.BillingApi/CalculateMonthlyBillexample-1-mcp-class/
InvoiceCalculator is an ordinary class: three public static methods, one public instance method, and a private rounding helper. The class has no MCP attributes and no Graftcode types.
host/gg.sh is the terminal snippet. The flags match Filter the callable surface:
gg ./InvoiceCalculator/bin/Debug/net9.0/InvoiceCalculator.dll \
--types Billing.InvoiceCalculator \
--methods CalculateMonthlyBill,ApplyTax \
--mcpBaseClass Billing.InvoiceCalculatorThe docs' own example is gg ./Pricing/bin/Debug/net9.0/Pricing.dll --types Pricing.PriceService --methods Calculate. This sample uses the same flags on its own assembly.
--mcpBaseClass is from Expose Receiver methods for MCP. Use it when tools/call sends a bare method name and params.class is empty.
host/mcp.json points at http://localhost:81/mcp, the URL in the .NET MCP quick start. Port 81 is the default Vision HTTP port (--httpPort in the Gateway CLI reference). That quick start also serves Graftcode Vision at http://localhost:81/GV.
The command names CalculateMonthlyBill and ApplyTax. LateFee is public and static and is left off the command. FormatInvoice is a public instance method and is also left off. Filter matching differs by runtime. Confirm the hosted surface in Vision before installing a Graft. This repo does not guess which members a given Gateway build will list.
--typesand--methodsselect the callable surface. Callable surface says that surface is an exposure boundary, not an authorization boundary. The flags do not authenticate or authorize a caller. Invocation auth is application-defined: Authentication and authorization operations.- The .NET MCP quick start says public methods are exposed as Grafts and that static methods are also MCP tools. Other sentences on that page, and the introduction, describe the MCP surface more broadly. Those sentences do not match. This sample follows the static-method sentence and does not describe instance members as MCP tools. Known limitations say MCP scope depends on the runtime and configuration.
- On Gateway 1.4.14 (the
gginstalled by the quick start command), a JSON number arrives boxed asInt32orDouble, and the CLR does not convert that toSystem.Decimal. This class usesdoublefor amounts andintfor units sotools/callcan bind. That is a binding choice for this sample, not a claim thatdoubleis the right ledger type. A JavaScript caller still cannot represent every fraction exactly (known limitations).
example-2-before-after/
This is the introduction's worked example on What is Graftcode?: illustrative REST on one side, a public Receiver method and a Caller that uses the installed Graft like local code on the other. That snippet is C# on both sides, which is why this example is C# even though the scenario list calls the same idea frontend to backend.
The React quick start is a different page. It installs a generated npm package, sets GraftConfig.host, and calls BillingLogic.calculateMonthlyBill with three arguments and a camelCase name. This repo does not recreate that app, and the two signatures are not the same method.
before-illustrative-rest/
BillingController— routePOST /api/v1/billing/monthly-billMonthlyBillDtos— request and response typesBillingHttpClient— handwritten client
The controller's _billing field is assigned in the constructor and used by the action. It is the Receiver, not a Graft.
after-receiver/BillingService.cs is the introduction's method: unitPrice * units. The sample rejects negative units. It does not round. Example 1's invoice calculator rounds to cents; that is a different class.
after-caller/MonthlyBillCaller.cs is the introduction's call shape:
GraftConfig.Host = "ws://billing.example/ws"; // before the first call
var billing = new BillingService();
return billing.CalculateMonthlyBill(unitPrice, units);The host string is the introduction's example. Copy the real host from Vision. For a network hop, the operations guide says to use wss://.
LocalGraftStandIn.cs is not a generated package. Package generation says package names come from the running Gateway or Vision and does not invent a naming formula. The stand-in stores Host, requires it before construction, and then calls the Receiver in-process. A remote Graft is still a network call. Setting Host to ws://billing.example/ws in this repo does not dial that address.
There is no _billing field on the Caller.
example-3-contracts/
The same operation, CalculateMonthlyBill (unit price times units), and the artifacts each path asks you to keep. This follows the comparison on the introduction and on Use Graftcode alongside an existing REST API: REST and gRPC keep a protocol contract separate from the method; with Graftcode the supported public method surface is the contract and the installed Graft is the client.
| Path | What the sample keeps |
|---|---|
rest/ |
Route, DTO, openapi.yaml, handwritten client |
grpc/ |
billing.proto and the Grpc.Tools codegen item. BillingGrpcService maps proto strings onto the method. The client and service base are generated at build time. |
graftcode/ |
The public method only. No route, DTO, OpenAPI document, or .proto. |
rest-host/ and grpc-host/ are not part of either contract. Each is a Program.cs that starts Kestrel for the folder next to it so the e2e suite can call it over TCP. They sit beside rest/ and grpc/ so those folders still show only the contract artifacts.
The three copies of the method are independent so each folder can be screenshotted on its own. The unit tests check that 10 * 5 and 19.99 * 3 agree in-process, and the e2e tests check the same two cases over REST, gRPC, and Gateway MCP.
Money on the REST document is a JSON number, which is not an exact decimal type. Money on the proto is a string so the fraction is not a binary float. The Graftcode method hosted for MCP uses double for amounts and int for units so JSON numbers bind on Gateway 1.4.14. The REST and gRPC sample methods still use decimal in-process; they are not started as MCP servers. Those are the contracts as written, not a ranking.
The docs say a call still depends on the runtime pair, execution mode, payload, transport, topology, and workload, and that the documentation does not publish comparative numbers. This repo does not add any.
- https://docs.graftcode.com/introduction/what-is-graftcode
- https://docs.graftcode.com/how-to-guides/filter-the-callable-surface
- https://docs.graftcode.com/core-concepts/callable-surface
- https://docs.graftcode.com/how-to-guides/expose-receiver-methods-for-mcp
- https://docs.graftcode.com/quick-start/expose-mcp/dotnet
- https://docs.graftcode.com/reference/gateway-cli-reference
- https://docs.graftcode.com/reference/known-limitations
- https://docs.graftcode.com/core-concepts/package-generation
- https://docs.graftcode.com/how-to-guides/use-graftcode-alongside-an-existing-rest-api
- https://docs.graftcode.com/operations/authentication-and-authorization-operations
- https://docs.graftcode.com/reference/project-key-registry-host-and-credentials
- https://docs.graftcode.com/quick-start
- https://docs.graftcode.com/how-to-guides/deploy-gateway-with-docker
- https://docs.graftcode.com/how-to-guides/run-gateway-locally
- https://docs.graftcode.com/reference/ports-and-protocols-reference
- https://docs.graftcode.com/operations/health-checks
- https://docs.graftcode.com/quick-start/expose-mcp/java