Expose interview tools with MCP
The practice function runs inside the agent service. Interview records belong to a different service, InterviewData. We’ll expose its repository operations through Model Context Protocol (MCP).
A local function is a good fit when your application owns the capability. MCP gives clients a common way to discover and call tools owned by a service. The same InterviewData server could support another agent application without moving its repository into that application.
That separation adds a connection and another service to operate. We’ll check the server independently before connecting the coach, so we can distinguish discovery failures from agent behavior.
Keep the coach’s local practice-guidance function for this lesson. In interview-coach-lab, we’ll work in the InterviewData project and root apphost.cs.
Describe the operations the server owns
Section titled “Describe the operations the server owns”MCP can expose several kinds of capabilities. This workshop uses tools. Each tool has a name, a description, and input metadata. Our server uses HTTP at /mcp. MCP also supports other transports.
Open src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.cs.
Update the required imports
File to edit: src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.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.ComponentModel;
using ModelContextProtocol;using System.ComponentModel;
using ModelContextProtocol;using ModelContextProtocol.Server;Mark the repository tool class for discovery
File to edit: src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.cs
Scope to edit: InterviewSessionTool type
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
public class InterviewSessionTool(IInterviewSessionRepository repository, ILogger<InterviewSessionTool> logger) : IInterviewSessionTool[McpServerToolType]public class InterviewSessionTool(IInterviewSessionRepository repository, ILogger<InterviewSessionTool> logger) : IInterviewSessionToolMcpServerToolType marks the class for discovery.
Next we’ll add an attribute to each method, starting with the operation that creates a record:
Expose add_interview_session
File to edit: src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.cs
Function to edit: AddInterviewSessionAsync
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
[Description("Creates a new interview session in the database. Use this after get_interview_session returns no record; update_interview_session cannot create one.")] [McpServerTool(Name = "add_interview_session", Title = "Add an interview session")] [Description("Creates a new interview session in the database. Use this after get_interview_session returns no record; update_interview_session cannot create one.")]Name becomes the public tool name. The existing descriptions explain the operation and its arguments to a client. The attribute exposes a method that already exists. The repository still implements persistence.
Apply the same pattern to listing, lookup, update, and completion. Notice that lookup retrieves a record, update changes an existing record, and completion is a separate operation:
Expose get_interview_sessions
File to edit: src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.cs
Function to edit: GetAllInterviewSessionsAsync
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
[Description("Gets a list of interview sessions from database.")] [McpServerTool(Name = "get_interview_sessions", Title = "Get a list of interview sessions")] [Description("Gets a list of interview sessions from database.")]Expose get_interview_session
File to edit: src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.cs
Function to edit: GetInterviewSessionAsync
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
[Description("Gets an interview session from the database by ID. Returns no record when the session has not been created.")] [McpServerTool(Name = "get_interview_session", Title = "Get an interview session")] [Description("Gets an interview session from the database by ID. Returns no record when the session has not been created.")]Expose update_interview_session
File to edit: src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.cs
Function to edit: UpdateInterviewSessionAsync
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
[Description("Updates an existing session: replaces the six document fields and APPENDS Transcript. Fetch first, preserve ResumeLink, ResumeText, ProceedWithoutResume, JobDescriptionLink, JobDescriptionText and ProceedWithoutJobDescription, and send ONLY NEW transcript text. Never resend the stored transcript. This cannot create a record or set IsCompleted; use add_interview_session or complete_interview_session for those operations.")] [McpServerTool(Name = "update_interview_session", Title = "Update an interview session")] [Description("Updates an existing session: replaces the six document fields and APPENDS Transcript. Fetch first, preserve ResumeLink, ResumeText, ProceedWithoutResume, JobDescriptionLink, JobDescriptionText and ProceedWithoutJobDescription, and send ONLY NEW transcript text. Never resend the stored transcript. This cannot create a record or set IsCompleted; use add_interview_session or complete_interview_session for those operations.")]Expose complete_interview_session
File to edit: src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.cs
Function to edit: CompleteInterviewSessionAsync
Replace the matching block with the code below. Open "Current code" to locate the block in your file.
Current code
[Description("Marks an existing interview session as complete and returns the saved record with IsCompleted true. Call this after saving the summary; update_interview_session cannot mark completion. This does not create a missing session.")] [McpServerTool(Name = "complete_interview_session", Title = "Complete an interview session")] [Description("Marks an existing interview session as complete and returns the saved record with IsCompleted true. Call this after saving the summary; update_interview_session cannot mark completion. This does not create a missing session.")]Publish the tools on an HTTP endpoint
Section titled “Publish the tools on an HTTP endpoint”In src/InterviewCoach.Mcp.InterviewData/Program.cs, register the MCP server, scan this assembly for tools, and map /mcp:
Update the required imports
File to edit: src/InterviewCoach.Mcp.InterviewData/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 InterviewCoach.Mcp.InterviewData;using System.Reflection;
using InterviewCoach.Mcp.InterviewData;Register the stateless HTTP MCP server and discover tools
File to edit: src/InterviewCoach.Mcp.InterviewData/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
var app = builder.Build();builder.Services.AddMcpServer() .WithHttpTransport(o => o.Stateless = true) .WithToolsFromAssembly(Assembly.GetEntryAssembly());
var app = builder.Build();Expose the repository server at /mcp
File to edit: src/InterviewCoach.Mcp.InterviewData/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
await app.RunAsync();app.MapMcp("/mcp");
await app.RunAsync();The existing database context and repository registrations stay in place.
Now apply the supplied resource declaration in root apphost.cs. It starts the Cosmos emulator and InterviewData, with database interviewdb and container interviewsessions.
Start the supplied repository service and emulator
File to edit: apphost.cs
Scope to edit: Top-level statements (no enclosing function)
Supplied setup to apply
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)// Azure Cosmos DB (NoSQL). Uses the local emulator in run mode and provisions a managed// account when published. Aspire creates the database and container as resources, so no// runtime resource creation is required (see the EnsureCreatedAsync note in the MCP server).var cosmos = builder.AddAzureCosmosDB(ResourceConstants.Cosmos);#pragma warning disable ASPIRECOSMOSDB001if (builder.ExecutionContext.IsRunMode){ cosmos.RunAsPreviewEmulator(emulator => emulator.WithDataExplorer());}#pragma warning restore ASPIRECOSMOSDB001
var cosmosDb = cosmos.AddCosmosDatabase(ResourceConstants.CosmosDatabase);cosmosDb.AddContainer(ResourceConstants.CosmosContainer, "/id");
var mcpInterviewData = builder.AddProject<Projects.InterviewCoach_Mcp_InterviewData>(ResourceConstants.McpInterviewData) .WithReference(cosmosDb) .WaitFor(cosmosDb);
var agent = builder.AddProject<Projects.InterviewCoach_Agent>(ResourceConstants.Agent)List the tools from your terminal
Section titled “List the tools from your terminal”Start your container engine, then run 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.csWait for mcp-interview-data to be running in the Aspire dashboard. Copy its HTTP endpoint, including the actual host and port. Replace http://ACTUAL-ENDPOINT below with that address and append /mcp once:
dotnet run --file tools/list-mcp-tools.cs -- http://ACTUAL-ENDPOINT/mcpdotnet run --file tools/list-mcp-tools.cs -- http://ACTUAL-ENDPOINT/mcpThe probe is supplied in your starter’s tools folder. It initializes an MCP client, calls ListToolsAsync, and prints each returned name. The output should include all five:
add_interview_sessionget_interview_sessionsget_interview_sessionupdate_interview_sessioncomplete_interview_sessionYou now have a discoverable server. Connecting those tools to the coach is the next lesson.
If the probe cannot discover the tools
For a connection error, compare the URL with the dashboard endpoint and confirm InterviewData is running. The terminal probe needs the dashboard’s host and port. If it connects but lists no tools, check the class and method attributes, assembly registration, and /mcp mapping.
The probe only lists tools. Creating a record comes next. Successful discovery narrows a problem to later operations, but it does not prove that storage calls will succeed.