Featured resource
2026 Tech Forecast
2026 Tech Forecast

1,500+ tech insiders, business leaders, and Pluralsight Authors share their predictions on what’s shifting fastest and how to stay ahead.

Download the forecast
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

Connecting MCP Tools in C# MCP Server

Your platform team wants engineers to ask an AI agent 'what's blocking the payments release?' and get a real answer pulled from live systems. You are building DevPulse, the MCP server that makes that possible. It must run two ways: as a STDIO process a developer's local agent can spawn directly, and as an ASP.NET Core HTTP service that a centrally hosted agent can call. It exposes focused tools, searching tickets in a database and checking build status from an internal API, and logs every call for tracing. Because tickets and builds contain data scoped to individual engineers, the HTTP transport must authenticate callers with bearer tokens and hide or block admin-only tools from ordinary viewers. An agent client you build must forward each user's token on every tool call so downstream data stays scoped correctly, and the server must also expose a prompt an agent can fetch alongside its tools. Finally, DevPulse needs to advertise who published it and which version is running, the same metadata a developer would check in VS Code's MCP server list or a NuGet package page before trusting it.

Lab platform
Lab Info
Level
Intermediate
Last updated
Oct 27, 2026
Duration
45m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. Challenge

    Step 1: Orientation to the DevPulse MCP server

    Your platform team wants an AI coding agent to answer "what's blocking the payments release?" by actually querying live systems, not guessing from a stale wiki page. That means giving the agent tools it can call: search open tickets, check a build's status, and pull a short summary prompt. The Model Context Protocol (MCP) is the wire format that lets any agent -- whether it's a CLI a developer spawns locally or a hosted assistant a whole team shares -- discover and invoke those tools the same way, regardless of which language or framework wrote them. Over this lab you'll build DevPulse, the MCP server that plays that role for your team's ticket and build data. Every MCP request, whatever transport carries it, boils down to the same shape: a method name (initialize, tools/list, tools/call, prompts/get) plus a bag of parameters. DevPulse centers on a single McpDispatcher that takes that shape and routes it to a registered handler -- a tool method, a prompt method, or the built-in handshake. Because the dispatcher doesn't know or care whether the request arrived over a STDIO pipe or an HTTP POST, you write the routing logic exactly once and reuse it behind both transports. That reuse is the architectural decision the rest of the lab leans on: transports translate bytes into requests, the dispatcher decides what happens next. Tickets and build data are scoped to individual engineers, so DevPulse can't just accept any HTTP call that shows up. You'll issue and validate OAuth-style bearer tokens, filter which tools a caller's role is even allowed to see in tools/list, and enforce that same rule if someone tries to invoke a hidden tool directly by name. Every tool call, successful or not, also gets recorded by a trace recorder so a platform engineer debugging a bad answer can see exactly which method ran, with what parameters, and how long it took -- observability that STDIO's local, single-user model doesn't force on you but a shared HTTP service does. The steps ahead mirror how you'd actually build this: Step 2 stands up the dispatcher and gives it both a STDIO loop and an ASP.NET Core HTTP endpoint. Step 3 adds the two real tools -- ticket search backed by EF Core, build status backed by a simulated internal API -- and wraps every call in tracing. Step 4 locks the HTTP transport behind bearer tokens and makes tool discovery role-aware. Step 5 builds an agent client that forwards each user's token downstream, adds a summarization prompt alongside the tools, and publishes the server metadata a developer would check before trusting DevPulse in their editor. By the end, DevPulse is a server you could actually hand to a team -- and in Step 6 you'll hand it to a real AI model and watch it answer the payments question with the tools you built.

    If you get stuck, you can refer to the provided solution code for each task, available in the solution folder.

  2. Challenge

    Step 2: Stand up STDIO and HTTP transports for the MCP dispatcher

    MCP's handshake starts with an initialize call: before an agent invokes any tool it asks the server what it supports, and the server answers with a protocol version and a capabilities summary. Every other call -- a tool invocation, a prompt fetch -- names a method and carries parameters, and the server is expected to route it to whatever handles that method. McpDispatcher is where DevPulse makes that routing decision once, so both transports you're about to build can call the exact same method and get the exact same behavior. With routing in place, DevPulse needs its first front door. A developer running an agent locally spawns your server as a child process and talks to it over its standard input and output -- no sockets, no ports, just newline-delimited JSON flowing in and out. That's the STDIO transport MCP defines for exactly this case: one process, one trusted caller, minimal setup. StdioTransport wraps McpDispatcher in a loop that reads a request per line and writes a response per line. A centrally hosted agent can't spawn a process on someone else's machine, so it needs the same dispatcher reachable over HTTP instead. ASP.NET Core lets you handle that without a full MVC controller: a single handler reads a JSON body off HttpContext, hands it to the dispatcher, and writes back whatever the dispatcher returns -- the same result shape, whether it came from a tool call or the initialize handshake. Getting both transports to answer initialize identically now is what lets you add tools once in Step 3 and have them show up correctly no matter which door an agent walked through.

  3. Challenge

    Step 3: Design focused tools and trace their protocol traffic

    An MCP tool should do one thing an agent can reason about: "search tickets," not "do anything with the ticket system." DevPulse's first tool, SearchTickets, reads from a TicketsDb -- an EF Core DbContext backed by the in-memory provider, seeded with a handful of tickets so you can query it exactly like a real SQL-backed context without standing up a database. Keeping the tool method itself thin -- query, project, return -- is what makes it easy to trace and secure later; the tool doesn't know anything about HTTP, STDIO, or auth, it just answers one question. Build status doesn't live in your ticket database -- it comes from an internal CI system reachable over its own API. BuildApiClient simulates that call so the lab doesn't depend on a real network, but the shape is the one you'd write against a live service: an async call that can return null for an unknown project. GetBuildStatus is the second tool, and giving it its own client dependency instead of reusing TicketsDb keeps the two tools independently testable and independently securable. Two tools now exist, and a hosted server means calls arrive from callers you can't watch over someone's shoulder. Wrapping every tool invocation with a trace recorder -- capturing the method name, its parameters, and how long it took -- turns "the agent got a weird answer" into something you can actually debug. Exposing that log through its own tool means an agent, or a developer testing it, can ask "what just happened here" without reaching for a separate log file.

  4. Challenge

    Step 4: Protect MCP endpoints with OAuth and role-aware discovery

    Ticket and build data is scoped per engineer, so the moment DevPulse's HTTP transport is reachable by more than one person, it needs to know who's calling. Bearer tokens are the standard answer: a caller presents a token in an Authorization: Bearer <token> header, and the server -- without any external identity provider in this lab -- issues and validates those tokens itself through an in-process TokenService. Validating a token means turning it back into a ClaimsPrincipal carrying the caller's user id and role, the same shape ASP.NET Core authorization already expects. A TokenService that can validate tokens is only useful if something actually calls it on every request. AuthMiddleware is that gate: it reads the Authorization header off the incoming HttpContext, validates it, and either attaches the resulting principal to the context or short-circuits the request with a 401 before the dispatcher ever sees it. This is the point where an unauthenticated HTTP call to DevPulse stops being possible -- STDIO stays trusted-by-default since it's one local process, but HTTP now checks every time. Authentication answers "who is this," authorization answers "what are they allowed to do" -- and for DevPulse that means an ordinary viewer shouldn't even see admin-only tools in tools/list, let alone invoke one by guessing its name. ToolRegistry holds the metadata that says which role a tool requires, and a single rule -- IsToolAllowedForRole -- needs to back both the filtered discovery list and the enforcement check on tools/call, so there's exactly one place that decision is made and exactly one place to get it right.

  5. Challenge

    Step 5: Connect a secured agent and package the server for distribution

    Tools aren't the only thing MCP servers expose -- a prompt is a piece of reusable instructional text a server hands an agent, the same way a tool hands it data. DevPulse's SummarizePendingTickets prompt takes the tickets SearchTickets already knows how to find and turns them into a message an agent's model can read directly, so "summarize what's pending" doesn't require the agent to invent its own phrasing every time. Every tool you've built so far assumes it's already running inside DevPulse with access to TicketsDb and BuildApiClient directly. An agent that calls DevPulse over HTTP is a different caller entirely, and it needs to carry the human user's identity with it -- not DevPulse's own service credentials -- so that a viewer using the agent still only sees what a viewer is allowed to see. AgentClient is that caller: it holds the user's bearer token and must attach it to every tool and prompt request it sends, the same header AuthMiddleware is already checking for on the way in. Before a developer lets any MCP server touch their editor, they check who published it and what version is running -- the same information VS Code's MCP panel or a NuGet package page surfaces. DevPulse advertises that through the initialize handshake's server info: a name, a version, a publisher, and a live count of what it currently offers, so trusting DevPulse doesn't require reading its source first.

  6. Challenge

    Step 6: Put an AI agent in front of DevPulse

    Every task is done and graded. Now you'll put DevPulse in front of a real AI model and watch it answer the question this lab started with -- "what's blocking the payments release?" -- using your transports, your auth layer, your tools, and your agent client, all at once.

    The agent is already written for you in src/DevPulseAgent.cs, and it's short enough to read in a minute. It asks DevPulse which tools the signed-in user may use (tools/list), offers exactly those tools to the model, and lets the model decide which to call. Every call the model makes goes back to DevPulse as tools/call through your AgentClient, carrying that user's bearer token -- so the model can never do more than the user it's working for.

    Add your API key

    The model is reached through the lab's AI service. Your API key can be found in the top center of your screen.

    1. Open the .env file at the root of the workspace.
    2. Paste the key after LAB_API_KEY=.

    Then confirm everything is connected. In the Terminal tab, run:

    dotnet run --project src/Lab.csproj -- --agent --check
    

    You should see three green check marks: the key was found, DevPulse answered tools/list, and the AI service replied. A yellow line says what to fix -- if DevPulse itself isn't answering, it names the task to revisit. ### Ask the question DevPulse was built for

    Start the agent:

    dotnet run --project src/Lab.csproj -- --agent
    

    This starts DevPulse's HTTP transport inside the same process and signs you in as demo-user, who has the viewer role. At the ask> prompt, type:

    what's blocking the payments release?
    

    The colored lines printed before the answer are the whole lab working together:

    • → model calls ... is the model choosing one of your tools and its arguments, based only on the names, descriptions, and input schemas tools/list returned.
    • [DevPulse] request from demo-user (role: viewer) is your HTTP transport accepting the bearer token AgentClient forwarded -- ValidateToken and AuthorizeRequest at work.
    • ← ... is what SearchTickets and GetBuildStatus sent back to the model.

    The answer that follows should mention the certificate ticket and the failed payments build. The model's wording changes from run to run; the tool calls are what to watch. ### Follow the trace and the prompt

    Type /trace to print DevPulse's trace log: every tools/list, tools/call, and prompts/get the agent made, with its parameters and timing, recorded by your RecordInvocation. This is what a platform engineer reads when an agent gives a strange answer.

    Type /prompt to fetch your summarize_pending_tickets prompt with prompts/get. The agent prints the prompt's text and sends it to the model as your message, so you can see your GetPrompt output steering the model. ### See role-aware discovery from the model's side

    Still signed in as the viewer, ask:

    please clear the trace log
    

    The model can't do it, and it should say so: tools/list never showed it purge_trace_log, because IsToolAllowedForRole hides admin-only tools from viewers. Even a client that guessed the tool's name would be refused, because tools/call runs the same check.

    Now switch to the admin user and ask again:

    /as admin
    please clear the trace log
    

    This time DevPulse offers three tools, and the model calls purge_trace_log with the admin's token. Type /trace once more to see the log start over.

About the author

Pluralsight’s AI authoring technology is designed to accelerate the creation of hands-on, technical learning experiences. Serving as a first-pass content generator, it produces structured lab drafts aligned to learning objectives defined by Pluralsight’s Curriculum team. Each lab is then enhanced by our Content team, who configure the environments, refine instructions, and conduct rigorous technical and quality reviews. The result is a collaboration between artificial intelligence and human expertise, where AI supports scale and efficiency, and Pluralsight experts ensure accuracy, relevance, and instructional quality, helping learners build practical skills with confidence.

Real skill practice before real-world application

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Learn by doing

Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.

Follow your guide

All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.

Turn time into mastery

On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.

Get started with Pluralsight