Leaders Logo

Model Context Protocol (MCP): Connecting Context, Agents, and Modern Software Architecture

Introduction

Modern systems that integrate LLM-based agents and intelligent assistants with corporate services require more than simple API calls. These systems require explicitly controlled context, standardized interfaces, clear access boundaries, and formal governance mechanisms. Scientific literature indicates that the absence of these elements leads to unpredictable behavior, auditing difficulties, and security risks in AI-based systems (AMERSHI et al., 2019).

Complementing this view, this paper presents a practical implementation of a MCP Server in .NET operating over a GraphQL context, detailing how to define and formalize context structures, access boundaries, and standardized interfaces. For language models (LLMs) to be robustly integrated into corporate systems, with security, auditability, and predictability, it is necessary to adopt architectures and governance mechanisms that consider access restrictions, traceability, and formal control mechanisms, avoiding unexpected behaviors or operational insecurity (KARRAS et al., 2025). The central aim is to provide a reproducible template for real scenarios, illustrated by APIs that return results for a potential chat.

Context of the Model Context Protocol (MCP)

The MCP can be understood as an integration contract between a host (client executing the model) and a server (provider of capabilities). Instead of the model “inventing” integrations, the MCP allows the host to discover and invoke explicitly defined capabilities:

  • Tools: invocable functions (e.g., querying orders via GraphQL with allowlist).
  • Resources: read-only artifacts that provide context (e.g., schema, policies, examples).
  • Prompts: interaction templates/shortcuts (e.g., a standardized command to fetch orders).

In modern architectures, this pattern reduces coupling and creates a single point to apply authorization, rate limiting, auditing, and data policies, preventing the model from having direct and unrestricted access to the backend.

Definition of Context

Context, in this article, is the set of information and constraints that determine what can be accessed, how it can be accessed, and what response format is accepted. In the proposed scenario, the context is materialized by:

  • A GraphQL API as the domain data source.
  • A MCP Server as the governance layer, with an allowlist of operations and parameter validation.
  • Resources documenting schema, policies, and output examples.
SVG Image of the Article

Agents and Controlled Execution

Agents (in the functional sense) are consumers of these capabilities: the host decides whether and when to call a tool, but the server defines the limits of what is allowed. In this way, execution remains detailed from the access perspective: each invocable action has a contract, validation, and auditing.

Modern Software Architecture

The implementation of the MCP benefits from microservices principles and API governance: well-defined responsibilities, security layers, observability, and decoupling. In particular, the combination of MCP + GraphQL is useful when flexibility of query is desired, as long as it is accompanied by limits (complexity, depth, and allowlist).

Implementation of MCP in .NET over GraphQL

In this section, a reproducible slice of the proposed architecture is presented, consisting of: (i) a minimal GraphQL API, (ii) a gateway with allowlist and timeout, (iii) domain tools with explicit intent, and (iv) a demonstration API capable of returning responses in chat format. The goal is to highlight how the MCP can operate over a GraphQL context in a secure, auditable, and predictable manner.

GraphQL Context: Minimal API (Hot Chocolate)

The domain context is exposed through a minimal GraphQL API, implemented with Hot Chocolate, providing only the types and queries strictly necessary for the orders domain (orders). This approach reduces the attack surface, facilitates governance, and creates an explicit context contract for consumption by agents and MCP tools.

Hot Chocolate is a GraphQL framework for .NET that enables the construction of strongly typed, schema-oriented APIs integrated into the ASP.NET ecosystem (MARJANOVIC, 2022). It automates schema generation from C# types, supports validation, middlewares, efficient data resolution, and extensions such as authorization and observability, making it suitable for exposing well-defined contexts in agent-oriented and MCP architectures.

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>();

var app = builder.Build();

app.MapGraphQL("/graphql");

app.Run();

public record Order(string Id, string CustomerName, decimal Total, string Status);

public sealed class Query
{
    public IEnumerable<Order> Orders(string? status = null)
    {
        var all = new[]
        {
            new Order("ord_1001", "Ana",   120.50m, "PAID"),
            new Order("ord_1002", "Bruno",  89.90m, "PENDING"),
            new Order("ord_1003", "Carla", 450.00m, "PAID"),
        };

        return status is null
            ? all
            : all.Where(o => o.Status.Equals(status, StringComparison.OrdinalIgnoreCase));
    }

    public Order? OrderById(string id) =>
        Orders().FirstOrDefault(o => o.Id == id);
}

GraphQL Gateway with Allowlist and Timeout

The gateway encapsulates access to the GraphQL endpoint and applies explicit control policies, including allowlist of operations, timeout, and cancellation. This layer prevents the execution of arbitrary queries, standardizes access to the context, and reduces risks associated with abuse, denial of service, or schema exploitation.

using System.Net.Http.Json;
using System.Text.Json;

public sealed class GraphQLGateway
{
    private readonly HttpClient _http;

    private static readonly HashSet<string> AllowedOperations =
        new(StringComparer.OrdinalIgnoreCase)
        {
            "Orders",
            "OrderById"
        };

    public GraphQLGateway(HttpClient http) => _http = http;

    public async Task<JsonElement> ExecuteAsync(
        string operationName,
        string query,
        object variables,
        CancellationToken ct)
    {
        if (!AllowedOperations.Contains(operationName))
            throw new InvalidOperationException(
                $"Operation '{operationName}' is not allowed.");

        using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        cts.CancelAfter(TimeSpan.FromSeconds(3));

        var payload = new
        {
            operationName,
            query,
            variables
        };

        using var resp =
            await _http.PostAsJsonAsync("/graphql", payload, cts.Token);

        resp.EnsureSuccessStatusCode();

        return await resp.Content.ReadFromJsonAsync<JsonElement>(
            cancellationToken: cts.Token);
    }
}

MCP Tools: Domain Operations (without Free GraphQL)

Instead of exposing a generic tool capable of executing any GraphQL query, intention-oriented tools are made available, each mapping to a specific business operation. This decision reduces risks, facilitates auditing, simplifies validations, and reinforces the principle of least privilege in the MCP.

using System.Text.Json;

public sealed class OrderTools
{
    private readonly GraphQLGateway _gql;

    public OrderTools(GraphQLGateway gql) => _gql = gql;

    // Tool: orders.search (suggested scope: orders.search)
    public Task<JsonElement> SearchOrdersAsync(
        string? status,
        CancellationToken ct)
    {
        const string operationName = "Orders";
        const string query = """
        query Orders($status: String) {
          orders(status: $status) {
            id
            customerName
            total
            status
          }
        }
        """;

        var variables = new { status };
        return _gql.ExecuteAsync(operationName, query, variables, ct);
    }

    // Tool: orders.getById (suggested scope: orders.read)
    public Task<JsonElement> GetOrderByIdAsync(
        string id,
        CancellationToken ct)
    {
        const string operationName = "OrderById";
        const string query = """
        query OrderById($id: String!) {
          orderById(id: $id) {
            id
            customerName
            total
            status
          }
        }
        """;

        var variables = new { id };
        return _gql.ExecuteAsync(operationName, query, variables, ct);
    }
}

Resources (Context) and Prompts (Flows)

Although the concrete implementation of an MCP Server varies depending on the SDK or framework used, the recommended content for resources and prompts remains stable and language-independent, serving as a foundation for governance and semantic alignment between agents.

  • Resource: Schema (resource://graphql/schema): snapshot of the schema or relevant subset (orders).
  • Resource: Policies (resource://policies/access): scopes, quotas, prohibited data, and auditing rules.
  • Resource: Examples (resource://examples/orders): output examples to standardize discourse and format.
  • Prompt: /ask-orders: standardized instruction — “use only orders.search and orders.getById; do not attempt other sources; do not request sensitive data.”

Demonstration API: Result in Chat Format

To illustrate the result without relying on a specific host, a REST API is created that returns a list of messages (role/content). This API simulates a flow: it interprets the user's text and calls governed tools.

Contract

  • POST /chat with { "text": "..." }
  • Response: { correlationId, messages: [{role, content}, ...] }
using System.Text.Json;
using Microsoft.AspNetCore.Http.HttpResults;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient<GraphQLGateway>(http =>
{
    http.BaseAddress = new Uri("https://localhost:5001"); // GraphQL API
});

builder.Services.AddScoped<OrderTools>();

var app = builder.Build();

app.MapPost("/chat", async Task<Ok<ChatResponse>> (
    ChatRequest req,
    OrderTools tools,
    HttpContext http,
    CancellationToken ct) =>
{
    var correlationId = http.TraceIdentifier;

    var messages = new List<ChatMessage>
    {
        new("system", "Use only allowlisted tools. Do not return PII. Respond concisely."),
        new("user", req.Text)
    };

    if (req.Text.Contains("pago", StringComparison.OrdinalIgnoreCase) ||
        req.Text.Contains("paid", StringComparison.OrdinalIgnoreCase))
    {
        messages.Add(new("assistant", "Consulting orders with status PAID via tool orders.search..."));
        var json = await tools.SearchOrdersAsync("PAID", ct);
        messages.Add(new("assistant", SummarizeOrders(json)));
        return TypedResults.Ok(new ChatResponse(correlationId, messages));
    }

    if (req.Text.Contains("ord_", StringComparison.OrdinalIgnoreCase))
    {
        var id = ExtractOrderId(req.Text);
        messages.Add(new("assistant", $"Consulting order {id} via tool orders.getById..."));
        var json = await tools.GetOrderByIdAsync(id, ct);
        messages.Add(new("assistant", SummarizeOrder(json, id)));
        return TypedResults.Ok(new ChatResponse(correlationId, messages));
    }

    messages.Add(new("assistant",
        "Examples: \"bring me paid orders\" or \"detail order ord_1002\"."));

    return TypedResults.Ok(new ChatResponse(correlationId, messages));
});

app.Run();

static string ExtractOrderId(string text)
{
    var idx = text.IndexOf("ord_", StringComparison.OrdinalIgnoreCase);
    if (idx < 0) return "ord_1001";
    var tail = text.Substring(idx);
    var end = tail.IndexOfAny([' ', '.', ',', ';', '\n', '\r']);
    return end < 0 ? tail : tail[..end];
}

static string SummarizeOrders(JsonElement gqlJson)
{
    if (!gqlJson.TryGetProperty("data", out var data) ||
        !data.TryGetProperty("orders", out var orders))
        return "No results found.";

    var lines = new List<string> { "Orders found:" };

    foreach (var o in orders.EnumerateArray())
    {
        var id = o.GetProperty("id").GetString();
        var name = o.GetProperty("customerName").GetString();
        var total = o.GetProperty("total").GetDecimal();
        var status = o.GetProperty("status").GetString();
        lines.Add($"- {id}: {name} — {status} — Total {total:C}");
    }

    return string.Join("\n", lines);
}

static string SummarizeOrder(JsonElement gqlJson, string id)
{
    if (!gqlJson.TryGetProperty("data", out var data) ||
        !data.TryGetProperty("orderById", out var order) ||
        order.ValueKind == JsonValueKind.Null)
        return $"Order {id} not found.";

    var name = order.GetProperty("customerName").GetString();
    var total = order.GetProperty("total").GetDecimal();
    var status = order.GetProperty("status").GetString();
    return $"Order {id}: {name} — {status} — Total {total:C}";
}

public sealed record ChatRequest(string Text);
public sealed record ChatResponse(string CorrelationId, List<ChatMessage> Messages);
public sealed record ChatMessage(string Role, string Content);

Discussion: Challenges and Opportunities

Interoperability

By standardizing tools/resources/prompts, the MCP reduces coupling between the host and internal services. However, real interoperability depends on explicit contracts, versioning, deprecation, and consistent documentation (resources).

Management of Contextual Data and Security

In GraphQL, flexibility is both a benefit and a risk (SABBAG FILHO, 2025). Therefore, designing with an allowlist and domain tools makes access more predictable. Additionally, auditing with CorrelationId allows tracking decisions and executions, an essential condition in regulated environments.

Scalability and Observability

Observability tools (structured logs, metrics, and tracing) become essential for identifying bottlenecks. As agents can increase the volume and variability of calls, quotas and caches per operation are recommended strategies to maintain predictability.

Conclusion

Building an MCP Server in .NET over a GraphQL backend allows combining query flexibility with rigorous governance. By applying clear premises, operation allowlists, access limits, and auditing, the MCP becomes an effective layer for integrating agents into corporate systems predictably. The demonstration API in chat format illustrates the presentation layer, while the separation between tools and gateway preserves security and maintainability.

References

  • AMERSHI, Saleema et al. Guidelines for human-AI interaction. In: Proceedings of the 2019 chi conference on human factors in computing systems. 2019. p. 1-13. reference.Description
  • KARRAS, Aristeidis et al. LLM-Driven Big Data Management Across Digital Governance, Marketing, and Accounting: A Spark-Orchestrated Framework. Algorithms, v. 18, n. 12, p. 791, 2025. reference.Description
  • MARJANOVIC, Rickard. Evaluating GraphQL over REST within a .NET Web API: A controlled experiment conducted by integrating with the Swedish Companies Registration Office. 2022. reference.Description
  • SABBAG FILHO, Nagib. Depth Control in GraphQL APIs with .NET: A Secure Approach to Mitigating DoS Attacks. Leaders Tec, vol. 2, no. 23, 2025. reference.Description
About the author