Skip to content

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.

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
Current code
using System.ComponentModel;
using ModelContextProtocol;

Updated code
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
Current code
public class InterviewSessionTool(IInterviewSessionRepository repository, ILogger<InterviewSessionTool> logger) : IInterviewSessionTool

Updated code
[McpServerToolType]
public class InterviewSessionTool(IInterviewSessionRepository repository, ILogger<InterviewSessionTool> logger) : IInterviewSessionTool

McpServerToolType 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
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.")]

Updated code
[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
Current code
[Description("Gets a list of interview sessions from database.")]

Updated code
[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
Current code
[Description("Gets an interview session from the database by ID. Returns no record when the session has not been created.")]

Updated code
[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
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.")]

Updated code
[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
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.")]

Updated code
[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.")]

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
Current code
using InterviewCoach.Mcp.InterviewData;

Updated code
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
Current code
var app = builder.Build();

Updated code
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
Current code
await app.RunAsync();

Updated code
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
Current code
var agent = builder.AddProject<Projects.InterviewCoach_Agent>(ResourceConstants.Agent)

Updated code
// 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 ASPIRECOSMOSDB001
if (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)

Start your container engine, then run from the working project root:

Terminal window
aspire stop --apphost ./apphost.cs
dotnet build InterviewCoach.slnx
aspire start --apphost ./apphost.cs

Wait 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:

Terminal window
dotnet run --file tools/list-mcp-tools.cs -- http://ACTUAL-ENDPOINT/mcp

The 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_session
get_interview_sessions
get_interview_session
update_interview_session
complete_interview_session

You 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.

Chapter 5 · Tools and interview context

Next: 6. Connect the coach to those tools