These hands-on tutorials guide you through customizing and extending the Interview Coach application. Work through them sequentially to build understanding.
Before starting:
- Complete the Getting Started setup
- Get the app running locally
- Skim the architecture overview
- Basic familiarity with C# and .NET
Goal: Trace how the interview process works from user input to agent response.
Duration: 20 minutes
Open src/InterviewCoach.Agent/AgentDelegateFactory.cs and find the CreateSingleAgent method:
instructions: """
You are an AI Interview Coach designed to help users prepare for job interviews.
...
Here's the overall process you should follow:
01. Start by fetching an existing interview session...
02. If there's no existing session, create a new interview session...
...
"""Exercise: Identify the key steps in the interview process. Notice:
- Session management comes first
- Resume/JD collection is optional
- Behavioral questions before technical
- User can stop at any time
The agent is configured with tools from two MCP servers:
var markitdownTools = markitdown.ListToolsAsync().GetAwaiter().GetResult();
var interviewDataTools = interviewData.ListToolsAsync().GetAwaiter().GetResult();
var agent = new ChatClientAgent(
chatClient: chatClient,
name: key,
instructions: """ ... """,
tools: [ .. markitdownTools, .. interviewDataTools ]
);Exercise:
- Start the application
- Open Aspire Dashboard (the URL appears in terminal)
- Navigate to Agent logs
- Start an interview conversation
- Watch the logs to see when tools are called
You should see entries like:
info: Calling tool: add_interview_session
info: Tool response: {"id": "..."}
The agent maintains state through the InterviewData MCP server:
- Open src/InterviewCoach.Mcp.InterviewData/InterviewSessionTool.cs
- Find the
UpdateInterviewSessionAsyncmethod - See how it stores resume, job description, and transcript
Exercise:
- Complete a short interview
- Check the database using the Cosmos DB emulator's Data Explorer
- Find your session record
- Examine the stored transcript JSON
Let's add a warmup message before behavioral questions.
Edit src/InterviewCoach.Agent/AgentDelegateFactory.cs:
Find this line:
07. Once you have updated the session record with the information, begin the interview by asking behavioral questions first.Change to:
07. Once you have updated the session record with the information, first provide a brief warmup message encouraging the user, then begin the interview by asking behavioral questions first.Test:
- Restart the application
- Start a new interview
- Notice the warmup message before questions begin
Reflection: How does changing instructions affect agent behavior without code changes?
Goal: Build a simple MCP server that provides interview tips.
Duration: 45 minutes
cd src
dotnet new web -n InterviewCoach.Mcp.Tips
cd InterviewCoach.Mcp.Tips
dotnet add package ModelContextProtocol.Server
dotnet add package Microsoft.Extensions.HostingCreate InterviewTipsTool.cs:
using System.ComponentModel;
using ModelContextProtocol.Server;
[McpServerToolType]
public class InterviewTipsTool
{
private static readonly Dictionary<string, string> Tips = new()
{
["behavioral"] = "Use the STAR method: Situation, Task, Action, Result",
["technical"] = "Think out loud. Explain your reasoning as you solve problems",
["general"] = "Prepare questions for the interviewer. Show genuine interest"
};
[McpServerTool(Name = "get_interview_tip", Title = "Get an interview tip")]
[Description("Get a helpful interview tip by category (behavioral, technical, or general).")]
public string GetInterviewTip(
[Description("Tip category: behavioral, technical, or general")] string category
)
{
return Tips.GetValueOrDefault(category, Tips["general"]);
}
}using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
.WithHttpTransport(o => o.Stateless = true)
.WithToolsFromAssembly(Assembly.GetEntryAssembly());
var app = builder.Build();
app.MapMcp("/mcp");
await app.RunAsync();Edit src/InterviewCoach.Agent/Program.cs:
Add after other MCP servers:
var mcpTips = builder.AddProject<Projects.InterviewCoach_Mcp_Tips>("mcp-tips")
.WithExternalHttpEndpoints();Update agent reference:
var agent = builder.AddProject<Projects.InterviewCoach_Agent>(ResourceConstants.Agent)
.WithExternalHttpEndpoints()
.WithLlmReference(builder.Configuration, args)
.WithEnvironment(ResourceConstants.LlmProvider, builder.Configuration[ResourceConstants.LlmProvider] ?? string.Empty)
.WithReference(mcpMarkItDown.GetEndpoint("http"))
.WithReference(mcpInterviewData)
.WithReference(mcpTips) // Add this line
.WaitFor(mcpMarkItDown)
.WaitFor(mcpInterviewData)
.WaitFor(mcpTips); // Add this lineEdit src/InterviewCoach.Agent/Program.cs:
Add HTTP client:
builder.Services.AddHttpClient("mcp-tips", client =>
{
client.BaseAddress = new Uri("https+http://mcp-tips");
});Add MCP client:
builder.Services.AddKeyedSingleton<McpClient>("mcp-tips", (sp, obj) =>
{
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
var httpClient = sp.GetRequiredService<IHttpClientFactory>().CreateClient("mcp-tips");
var clientTransportOptions = new HttpClientTransportOptions()
{
Endpoint = new Uri($"{httpClient.BaseAddress!.ToString().Replace("+http", string.Empty).TrimEnd('/')}/mcp")
};
var clientTransport = new HttpClientTransport(clientTransportOptions, httpClient, loggerFactory);
var clientOptions = new McpClientOptions()
{
ClientInfo = new Implementation()
{
Name = "MCP Tips Client",
Version = "1.0.0",
}
};
return McpClient.CreateAsync(clientTransport, clientOptions, loggerFactory).GetAwaiter().GetResult();
});Register tools with agent:
var markitdown = sp.GetRequiredKeyedService<McpClient>("mcp-markitdown");
var interviewData = sp.GetRequiredKeyedService<McpClient>("mcp-interview-data");
var tips = sp.GetRequiredKeyedService<McpClient>("mcp-tips"); // Add this
var markitdownTools = markitdown.ListToolsAsync().GetAwaiter().GetResult();
var interviewDataTools = interviewData.ListToolsAsync().GetAwaiter().GetResult();
var tipsTools = tips.ListToolsAsync().GetAwaiter().GetResult(); // Add this
var agent = new ChatClientAgent(
chatClient: chatClient,
name: key,
instructions: """ ... """,
tools: [ .. markitdownTools, .. interviewDataTools, .. tipsTools ] // Add tipsTools
);Add to the agent instructions:
instructions: """
You are an AI Interview Coach...
...
Use the provided tools to manage interview sessions, capture resume and job description,
ask questions, analyze responses, provide interview tips when appropriate, and generate summaries.
...
"""- Restart the application
- During an interview, ask: "Can you give me a tip for behavioral questions?"
- The agent should call
get_interview_tipwith category "behavioral"
Challenge: Add more tips to the dictionary and enhance the tool to return multiple tips.
Goal: Modify agent behavior to focus on a specific job type.
Duration: 30 minutes
Edit src/InterviewCoach.Agent/AgentDelegateFactory.cs:
Find the CreateSingleAgent method and change the opening:
instructions: """
You are an AI Interview Coach specializing in backend software engineering positions.
You will guide users through technical interview preparation with a focus on:
- System design and architecture
- API design and REST principles
- Database modeling and optimization
- Microservices and distributed systems
- Algorithm and data structure fundamentals
...
"""Change the question flow in AgentDelegateFactory.cs:
07. Once you have updated the session record with the information, begin the interview with
system design questions first (2-3 questions).
08. After system design, move to algorithm and data structures questions (2-3 questions).
09. Finally, ask about specific backend technologies mentioned in the job description.
10. Before switching categories, ask the user if they want to continue to the next section.Include specific context:
When asking system design questions, focus on:
- Scalability patterns (load balancing, caching, sharding)
- Data consistency models (CAP theorem, eventual consistency)
- API versioning strategies
- Authentication/authorization patterns
When asking algorithm questions:
- Start with problem clarification
- Encourage the user to explain their approach
- Ask about time and space complexity
- Discuss optimization opportunities- Restart the application
- Start an interview with a backend job description
- Notice the changed question types and focus
Extension: Create different agent profiles for frontend, data science, or DevOps roles.
Goal: Understand how to extend to multiple specialized agents.
Duration: 45 minutes (conceptual + starter code)
Instead of one general interview coach, create:
- Recruiter Agent: Screens candidates, asks behavioral questions
- Technical Agent: Conducts technical assessment
- Coordinator Agent: Manages the flow between agents
Create src/InterviewCoach.Agent/AgentRoles.cs:
public static class AgentRoles
{
public const string Coordinator = "coordinator";
public const string Recruiter = "recruiter";
public const string Technical = "technical";
}public static class AgentInstructions
{
public static string GetInstructions(string role) => role switch
{
AgentRoles.Recruiter => """
You are a friendly HR recruiter conducting the initial screening.
Focus on:
- Cultural fit and soft skills
- Communication ability
- Motivation and interest in the role
- Work history and experience alignment
Ask 3-5 behavioral questions using the STAR framework.
After completing your questions, hand off to the technical interviewer.
""",
AgentRoles.Technical => """
You are a senior engineer conducting the technical interview.
Focus on:
- Technical problem-solving ability
- System design thinking
- Code quality and best practices
- Technology depth
Conduct 2-3 technical assessments.
After completing, provide your assessment to the coordinator.
""",
AgentRoles.Coordinator => """
You coordinate the interview process.
- Welcome the candidate
- Introduce them to the recruiter agent
- After recruiting, transition to technical agent
- Collect feedback from both agents
- Provide final summary
""",
_ => throw new ArgumentException($"Unknown role: {role}")
};
}Modify Program.cs:
// Register coordinator agent
builder.AddAIAgent(
name: AgentRoles.Coordinator,
createAgentDelegate: (sp, key) => CreateAgent(sp, key, AgentRoles.Coordinator)
);
// Register recruiter agent
builder.AddAIAgent(
name: AgentRoles.Recruiter,
createAgentDelegate: (sp, key) => CreateAgent(sp, key, AgentRoles.Recruiter)
);
// Register technical agent
builder.AddAIAgent(
name: AgentRoles.Technical,
createAgentDelegate: (sp, key) => CreateAgent(sp, key, AgentRoles.Technical)
);
static ChatClientAgent CreateAgent(IServiceProvider sp, string key, string role)
{
var chatClient = sp.GetRequiredService<IChatClient>();
var markitdown = sp.GetRequiredKeyedService<McpClient>("mcp-markitdown");
var interviewData = sp.GetRequiredKeyedService<McpClient>("mcp-interview-data");
var tools = new List<Tool>();
tools.AddRange(markitdown.ListToolsAsync().GetAwaiter().GetResult());
tools.AddRange(interviewData.ListToolsAsync().GetAwaiter().GetResult());
return new ChatClientAgent(
chatClient: chatClient,
name: key,
instructions: AgentInstructions.GetInstructions(role),
tools: tools
);
}The coordinator would handle transitions:
sequenceDiagram
participant U as User
participant C as Coordinator Agent
participant R as Recruiter Agent
participant T as Technical Agent
U->>C: Connect
C->>U: "Let me introduce you to the Recruiter"
C-->>R: Handoff
loop Behavioral Interview
R->>U: Ask behavioral question
U->>R: Answer
end
R-->>C: Complete → return to Coordinator
C->>U: "Now let's move to the technical interview"
C-->>T: Handoff
loop Technical Assessment
T->>U: Ask technical question
U->>T: Answer
end
T-->>C: Complete → return to Coordinator
C->>C: Aggregate feedback
C->>U: Final summary
Note: Full multi-agent orchestration requires additional workflow management. See Microsoft Agent Framework Orchestrations for advanced patterns.
Goal: Enhance the agent to provide structured feedback scoring.
Duration: 30 minutes
Create src/InterviewCoach.Mcp.InterviewData/Models/Evaluation.cs:
public class InterviewEvaluation
{
public string SessionId { get; set; } = string.Empty;
public int CommunicationScore { get; set; } // 1-10
public int TechnicalScore { get; set; } // 1-10
public int ProblemSolvingScore { get; set; } // 1-10
public List<string> Strengths { get; set; } = new();
public List<string> AreasForImprovement { get; set; } = new();
public string OverallRecommendation { get; set; } = string.Empty;
}using System.ComponentModel;
using ModelContextProtocol.Server;
[McpServerToolType]
public class EvaluationTool(IInterviewSessionRepository repository)
{
[McpServerTool(Name = "save_interview_evaluation", Title = "Save interview evaluation")]
[Description("Save structured evaluation scores and feedback for an interview session.")]
public async Task<InterviewEvaluation> SaveEvaluationAsync(
[Description("The evaluation data")] InterviewEvaluation evaluation
)
{
await repository.SaveEvaluationAsync(evaluation);
return evaluation;
}
}11. After the interview is complete, generate a comprehensive evaluation including:
- Communication score (1-10)
- Technical score (1-10)
- Problem-solving score (1-10)
- List of strengths (3-5 items)
- Areas for improvement (3-5 items)
- Overall hiring recommendation
12. Save the evaluation using the save_interview_evaluation tool.
13. Present the evaluation to the user in a friendly, encouraging format.After these tutorials you should be able to:
- Change agent behavior by editing instructions
- Build a custom MCP server with tools
- Understand multi-agent handoff patterns
- Add structured evaluation logic
- Voice input — integrate speech-to-text for realistic practice
- Timer — add an MCP tool to track time per question
- Question bank — MCP server with curated questions by role/level
- Video analysis — MCP server for facial expression feedback (advanced)
- Session comparison — diff multiple interview sessions
If you build something interesting on top of this, open a PR or start a discussion on GitHub.