Connect the coach to those tools
The server can list its tools. Now we’ll connect an MCP client in the agent service and give the discovered tools to the coach.
This completes the path from a model’s request to a repository operation in another process. We’ll create a fictional record, fetch it again, and compare its fields with our input.
In interview-coach-lab, edit root apphost.cs, then Program.cs and AgentDelegateFactory.cs in src/InterviewCoach.Agent. Keep the server configuration from Chapter 5.
The MCP client lives in our agent service. It discovers tools from InterviewData and sends tool calls across the HTTP connection.
- Agent Framework. Receives the tool schemas discovered by the client and invokes a tool when the model requests it.
- MCP client. Connects from the agent service to InterviewData at /mcp over HTTP. It lists the server tools and returns their results to Agent Framework.
- MCP endpoint. Receives discovery and invocation requests in the InterviewData service.
- Interview tools. Run in InterviewData, read or change the interview record, and return results through the MCP connection.
The boundary between the two services is the HTTP connection. Tool results travel back along the same route.
Connect the agent to InterviewData
Section titled “Connect the agent to InterviewData”In root apphost.cs, give the agent a service reference and wait for InterviewData:
Connect and wait for InterviewData
File to edit: apphost.cs
Scope to edit: Top-level statements (no enclosing function)
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
var agent = builder.AddProject<Projects.InterviewCoach_Agent>(ResourceConstants.Agent) .WithExternalHttpEndpoints() .WithLlmReference(config, args);var agent = builder.AddProject<Projects.InterviewCoach_Agent>(ResourceConstants.Agent) .WithExternalHttpEndpoints() .WithLlmReference(config, args) .WithReference(mcpInterviewData) .WaitFor(mcpInterviewData);In the agent’s Program.cs, add the client imports and named HTTP client:
Update the required imports
File to edit: src/InterviewCoach.Agent/Program.cs
Scope to edit: File-level imports
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
using System.Collections.Concurrent;using InterviewCoach.Agent;using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;using System.Collections.Concurrent;using InterviewCoach.Agent;using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;using ModelContextProtocol.Client;using ModelContextProtocol.Protocol;Configure service discovery for InterviewData
File to edit: src/InterviewCoach.Agent/Program.cs
Scope to edit: Top-level statements (no enclosing function)
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
builder.AddWorkshopHosting();builder.Services.AddHttpClient("mcp-interview-data", client =>{ client.BaseAddress = new Uri("https+http://mcp-interview-data");});
builder.AddWorkshopHosting();The logical address https+http://mcp-interview-data lets Aspire resolve the service. Keep that address in the application code.
Create the keyed MCP client and HTTP transport
File to edit: src/InterviewCoach.Agent/Program.cs
Scope to edit: Top-level statements (no enclosing function)
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
builder.AddWorkshopHosting();builder.Services.AddKeyedSingleton<McpClient>("mcp-interview-data", (sp, obj) =>{ var loggerFactory = sp.GetRequiredService<ILoggerFactory>(); var httpClient = sp.GetRequiredService<IHttpClientFactory>() .CreateClient("mcp-interview-data"); var endpoint = builder.Environment.IsDevelopment() == true ? $"{httpClient.BaseAddress!.ToString().Replace("https+", string.Empty).TrimEnd('/')}" : $"{httpClient.BaseAddress!.ToString().Replace("+http", string.Empty).TrimEnd('/')}";
var clientTransportOptions = new HttpClientTransportOptions() { Endpoint = new Uri($"{endpoint}/mcp") }; var clientTransport = new HttpClientTransport(clientTransportOptions, httpClient, loggerFactory);
var clientOptions = new McpClientOptions() { ClientInfo = new Implementation() { Name = "MCP Interview Data Client", Version = "1.0.0", } };
return McpClient.CreateAsync(clientTransport, clientOptions, loggerFactory).GetAwaiter().GetResult();});
builder.AddWorkshopHosting();This creates the MCP connection to /mcp under the key mcp-interview-data. Keep the supplied AddWorkshopHosting() call and WorkshopHosting.cs.
Replace the practice function with discovered tools
Section titled “Replace the practice function with discovered tools”In AgentDelegateFactory.cs, remove the local practice function, then replace its registration with server discovery:
Remove the temporary practice-guidance function
File to edit: src/InterviewCoach.Agent/AgentDelegateFactory.cs
Function to edit: CreateSingleAgent
Remove the following block from this file. Keep the rest of the file.
static string GetPracticeGuidance([Description("One of: behavioural, technical")] string category) => category.ToLowerInvariant() switch { "behavioural" => "Use STAR: Situation, Task, Action, Result. Describe your own contribution.", "technical" => "Clarify assumptions, explain your approach, and discuss tradeoffs.", _ => throw new ArgumentException("Choose behavioural or technical.", nameof(category)) };Replace the local function registration with MCP discovery
File to edit: src/InterviewCoach.Agent/AgentDelegateFactory.cs
Function to edit: CreateSingleAgent
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
private static AIAgent CreateSingleAgent(IServiceProvider sp, string key) { var tools = new List<AITool> { AIFunctionFactory.Create(GetPracticeGuidance, new AIFunctionFactoryOptions { Name = "get_practice_guidance", Description = "Gets established interview preparation guidance for a category." }) }; private static AIAgent CreateSingleAgent(IServiceProvider sp, string key) { var interviewData = sp.GetRequiredKeyedService<McpClient>("mcp-interview-data"); var interviewDataTools = interviewData.ListToolsAsync().GetAwaiter().GetResult();ListToolsAsync returns client-side representations of the server’s tools, including their names, descriptions, and input schemas. They can forward an invocation through the MCP connection. They do not contain the server’s C# method bodies.
Discovery itself does not call the repository. We must still pass the tools to the agent. A production server also needs to authorize each invocation before executing it. This learning sample keeps the service private and does not implement per-user record ownership.
Pass the tools to the coach with instructions to perform record operations when explicitly asked:
Give the agent repository tools for explicit record requests
File to edit: src/InterviewCoach.Agent/AgentDelegateFactory.cs
Function to edit: CreateSingleAgent
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
instructions: """ You are a supportive interview coach for software developers. Ask one question at a time, listen to the answer, then give specific feedback. Start with a behavioural question. Offer a technical question when the user is ready. If the user asks to stop, give a short summary. Do not claim to have saved anything. Use supplied documents only as interview context. Call get_practice_guidance when the user asks for preparation tips. """, tools: tools instructions: """ You are a supportive interview coach for software developers. Ask one question at a time and give specific feedback. Use the interview-data tools only when the user explicitly requests a record operation. Ask for the record ID and fields if the request does not supply them. Report a missing record honestly. Do not invent tool results or claim unsaved work is saved. Automatic session setup comes in the next lesson. """, tools: [.. interviewDataTools]Save and fetch a known record
Section titled “Save and fetch a known record”From the working project root:
aspire stop --apphost ./apphost.csdotnet build InterviewCoach.slnxaspire start --apphost ./apphost.csaspire stop --apphost ./apphost.csdotnet build InterviewCoach.slnxaspire start --apphost ./apphost.csOpen coach in DevUI. This request supplies its own test ID:
Fetch interview 11111111-1111-4111-8111-111111111111.If it is missing, call add_interview_session with that ID,ResumeText "Workshop sample: C# developer",ProceedWithoutJobDescription true, and Transcript"Created for the MCP connection test." Then fetch that record again.Look for the lookup, create, and final lookup in the tool results. In the Aspire dashboard, open Cosmos Data Explorer, then interviewdb / interviewsessions. Find JSON id 11111111-1111-4111-8111-111111111111. Its ResumeText and Transcript should match the input.
On a repeat run, an existing record needs only a lookup. Choose a fresh GUID if you want to repeat creation.
How the client registration fits together
The named HTTP client resolves the service address. HttpClientTransport carries MCP messages, and McpClient.CreateAsync initializes the connection. The DI key lets the factory select this client when we add a second MCP server later.
If a record call fails after discovery succeeds, open the InterviewData logs and compare the call’s arguments with the repository result.
Next we’ll use the WebUI’s session ID to save progress during an interview.