It's tollow up on the AI 102 exam. Its all about buildin en deploying generative AI applicaties and production ready agents using Microsoft Foundry and AI Services. Where AI 102 covers subject like
- Vision
| Subtopic | Key Skills | Study Resource |
|---|---|---|
| Select Azure AI Services | Choose the right service for GenAI, NLP, vision, speech, search | docs.microsoft.com/azure/ai-services |
| Plan & Deploy with Foundry | Create hub/project, deploy models, CI/CD integration, containers | Microsoft Foundry documentation |
Microsofrt foundry?
Microsoft Foundry is a unified platform for building AI-powered apps and agents on Azure. It gives you access to thousands of AI models (GPT, Claude, open-source), ready-to-use AI APIs for vision, speech, language and document processing, and a full agent service to build autonomous workflows. You can ground your apps on your own data with RAG, monitor and govern everything in one place, and deploy to any target — cloud, containers, or edge.
As a Developer, Concretely You Can: Chat/GenAI apps — deploy GPT, Claude, or open models and call them via API\- RAG systems — connect your documents/data and build Q&A over them
- Agents — build bots that use tools, search, call APIs, and take actions
- Multi-agent pipelines — orchestrate multiple agents working together
- Fine-tune models — adapt base models on your own data
- Deploy anywhere — containers, serverless, edge, or local with Foundry Local
- Publish to Teams/M365 — ship agents to Teams and M365 with one click using low-code/no-code tools
MCP
What is MCP?MCP (Model Context Protocol) is an open standard for connecting AI assistants to the systems where data lives — content repositories, business tools, and development environments. Instead of maintaining separate connectors for each data source, developers build against one standard protocol. CBT NuggetsThink of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect devices to peripherals, MCP provides a standardized way to connect AI models to different data sources and tools. FlashgeniusImportantly, MCP is not an agent framework — it's a standardized integration layer. It doesn't decide when a tool is called; the LLM does that. MCP simply provides a standardized connection to streamline tool integration. Microsoft LearnMCP was introduced by Anthropic in November 2024 and has since been adopted by OpenAI, Google DeepMind, and many toolmakers as an industry standard.
How it works
The LLM receives a prompt. The LLM decides what to do it. It knows its registered MCP. TheMCP should have a clear description of what its capable of and what properties it needs to execute
```
{
"tools": [
{
"name": "get_weather",
"description": "Get the current weather for a city. Use this when the user asks about weather, temperature, or climate conditions in a specific location.",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city, e.g. 'Amsterdam'"
}
},
"required": ["city"]
}
}
]
}
```
This manifest gets injected into the LLM's **system prompt** before the conversation starts. So the LLM literally reads a description of every available tool, just like you'd read a menu.
**The `description` field is everything.** That's the plain English sentence that tells the LLM *when* to use the tool. The LLM matches the user's intent against those descriptions to decide which tool fits. If your description is vague or wrong, the LLM will either call the wrong tool or not call it at all.
So the full picture is
:``` MCP server starts
↓
Sends tool manifest (name + description + schema) to the runtime
↓
Runtime injects it into the LLM system prompt
↓
User sends a prompt → LLM reads it against the known tools
↓
LLM pattern-matches intent to the right tool description
↓
Calls it with correctly structured args (validated against the schema)
```
Part 1 — The MCP Server (tool registration) The [McpServerTool] attribute with Description = "..." is the tool manifest. That description string is literally what gets injected into the LLM system prompt — it's how the LLM knows when to call GetWeather vs Calculate. The [Description] on each parameter maps to the inputSchema, telling the LLM how to construct the arguments.
Part 2 — The Client + Agent Loop (automatic) mcpClient.ListToolsAsync() fetches the manifest from the server, t.AsAIFunction() converts each tool into an LLM function definition, and UseFunctionInvocation() handles the whole loop automatically — LLM calls tool → MCP executes it → result goes back to LLM → repeat until done.
Part 3 — The Manual Loop (what actually happens under the hood) This strips away the magic and shows each step explicitly:
The agnet
[McpServerToolType]
public static class TimeTools
{
[McpServerTool,
Description("Get the current date and time for a timezone. " +
"Use this when the user asks what time it is, " +
"or what time it is in a specific city or country.")]
public static Task<string> GetCurrentTime(
[Description("IANA timezone, e.g. 'Europe/Amsterdam' or 'Asia/Tokyo'. " +
"Defaults to UTC if not specified.")] string? timezone = null)
{
var tz = timezone != null
? TimeZoneInfo.FindSystemTimeZoneById(timezone)
: TimeZoneInfo.Utc;
var time = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
return Task.FromResult(
$"Current time in {tz.DisplayName}: {time:dddd, MMMM dd yyyy HH:mm:ss}");
}
}
```
// ============================================================
// MCP Agent Demo — C#
// Shows: tool manifest registration, agent loop, tool dispatch
//
// NuGet packages needed:
// dotnet add package Azure.AI.OpenAI
// dotnet add package ModelContextProtocol (official MS SDK)
// dotnet add package Microsoft.Extensions.AI
// ============================================================
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.AI.OpenAI;
using ModelContextProtocol.Server;
using ModelContextProtocol.Client;
using Microsoft.Extensions.AI;
// ============================================================
// PART 1 — THE MCP SERVER
// This is what registers tools and their descriptions.
// The LLM reads these descriptions to know WHAT each tool does.
// ============================================================
[McpServerToolType]
public static class WeatherTools
{
// The [McpServerTool] attribute is the tool manifest.
// The Description = "..." is exactly what gets injected
// into the LLM system prompt — this is how the LLM knows
// when and why to call this tool.
[McpServerTool,
Description("Get the current weather for a city. " +
"Use this when the user asks about weather, " +
"temperature, or climate in a specific location.")]
public static async Task GetWeather(
[Description("The city name, e.g. 'Amsterdam'")] string city)
{
// In production: call a real weather API here
// This simulates an MCP tool execution
await Task.Delay(200); // simulate network call
return city.ToLower() switch
{
"amsterdam" => "Amsterdam: 17°C, mostly cloudy",
"madrid" => "Madrid: 11°C, light rain",
"paris" => "Paris: 18°C, partly sunny",
"london" => "London: 13°C, overcast",
_ => $"{city}: {Random.Shared.Next(5, 25)}°C, variable"
};
}
}
[McpServerToolType]
public static class MathTools
{
[McpServerTool,
Description("Perform a mathematical calculation. " +
"Use this for any arithmetic, percentages, or numeric operations.")]
public static Task Calculate(
[Description("A math expression, e.g. '15% of 847' or '(42 * 3) + 7'")] string expression)
{
// In production: use a proper expression evaluator
// For demo: handle simple percentage pattern
if (expression.Contains('%'))
{
var parts = expression.ToLower().Replace("% of", "").Split(' ',
StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2
&& double.TryParse(parts[0], out var pct)
&& double.TryParse(parts[1], out var num))
{
var result = (pct / 100) * num;
return Task.FromResult($"{expression} = {result:F2}");
}
}
return Task.FromResult($"Calculated: {expression} = [result]");
}
}
[McpServerToolType]
public static class TimeTools
{
[McpServerTool,
Description("Get the current date and time for a timezone. " +
"Use this when the user asks what time it is, " +
"or what time it is in a specific city or country.")]
public static Task GetCurrentTime(
[Description("IANA timezone, e.g. 'Europe/Amsterdam' or 'Asia/Tokyo'. " +
"Defaults to UTC if not specified.")] string? timezone = null)
{
var tz = timezone != null
? TimeZoneInfo.FindSystemTimeZoneById(timezone)
: TimeZoneInfo.Utc;
var time = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
return Task.FromResult(
$"Current time in {tz.DisplayName}: {time:dddd, MMMM dd yyyy HH:mm:ss}");
}
}
// ============================================================
// PART 2 — THE MCP CLIENT + AGENT LOOP
// This is the glue between the LLM and the MCP server.
// It:
// 1. Connects to the MCP server
// 2. Fetches the tool manifest
// 3. Injects tools into the LLM
// 4. Runs the agentic loop (LLM → tool call → result → LLM)
// ============================================================
public class McpAgentDemo
{
public static async Task Main(string[] args)
{
Console.WriteLine("=== MCP Agent Demo ===\n");
// ── Step 1: Start the MCP server (in-process for demo)
// In production this would be a separate process or remote server
await using var mcpServer = await McpServerFactory.CreateAsync(
new McpServerOptions
{
ServerInfo = new() { Name = "demo-server", Version = "1.0" }
});
// ── Step 2: Connect MCP client to server
await using var mcpClient = await McpClientFactory.CreateAsync(
new McpClientOptions
{
ClientInfo = new() { Name = "agent-client", Version = "1.0" }
});
// ── Step 3: Fetch tool manifest from MCP server
// This is the JSON with name + description + inputSchema
// that gets injected into the LLM system prompt
var mcpTools = await mcpClient.ListToolsAsync();
Console.WriteLine("Tools registered via MCP manifest:");
foreach (var tool in mcpTools)
{
Console.WriteLine($" [{tool.Name}] — {tool.Description}");
}
Console.WriteLine();
// ── Step 4: Set up the LLM (Azure OpenAI)
var openAiClient = new AzureOpenAIClient(
new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
new System.ClientModel.ApiKeyCredential(
Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY")!));
var chatClient = openAiClient
.GetChatClient("gpt-4o")
.AsBuilder()
.UseFunctionInvocation() // auto-handles tool call loop
.Build();
// ── Step 5: Convert MCP tools → AI tools for the LLM
// This is where the manifest becomes LLM function definitions
var aiTools = mcpTools
.Select(t => t.AsAIFunction())
.ToList();
// ── Step 6: Run the agent
await RunAgentAsync(chatClient, aiTools, mcpClient);
}
static async Task RunAgentAsync(
IChatClient chatClient,
List tools,
IMcpClient mcpClient)
{
// System prompt tells the LLM it has tools available
// The tool descriptions (from the manifest) are appended automatically
var messages = new List
{
new(ChatRole.System,
"You are a helpful agent with access to tools via MCP. " +
"Use tools whenever they would help answer the user's question. " +
"Always explain what tool you're using and why.")
};
// Demo queries — showing different tools being called
string[] userQueries =
[
"What's the weather like in Amsterdam right now?",
"What is 23% of 1540?",
"What time is it in Tokyo?",
"Is it warmer in Paris or Amsterdam today?" // triggers TWO tool calls
];
foreach (var query in userQueries)
{
Console.WriteLine($"User: {query}");
Console.WriteLine(new string('-', 50));
messages.Add(new(ChatRole.User, query));
// ── The agent loop
// UseFunctionInvocation() handles this automatically:
// LLM response → detect tool call → execute via MCP → inject result → LLM continues
var response = await chatClient.CompleteAsync(
messages,
new ChatOptions
{
Tools = tools,
ToolChoice = ChatToolChoice.Auto // LLM decides when to use tools
});
// Show what happened
foreach (var content in response.Message.Contents)
{
switch (content)
{
case TextContent text:
Console.WriteLine($"Agent: {text.Text}");
break;
case FunctionCallContent call:
Console.WriteLine($" → Calling MCP tool: {call.Name}");
Console.WriteLine($" Args: {JsonSerializer.Serialize(call.Arguments)}");
break;
case FunctionResultContent result:
Console.WriteLine($" ← Tool result: {result.Result}");
break;
}
}
messages.Add(response.Message);
Console.WriteLine();
}
}
}
// ============================================================
// PART 3 — MANUAL AGENT LOOP (no SDK magic)
// This shows exactly what happens under the hood,
// without the auto function invocation middleware.
// ============================================================
public class ManualAgentLoop
{
public static async Task RunManualLoopAsync(
IChatClient llm,
IMcpClient mcpClient,
string userPrompt)
{
Console.WriteLine("=== Manual MCP Agent Loop ===");
// Fetch tool manifest from MCP server
var mcpTools = await mcpClient.ListToolsAsync();
var aiTools = mcpTools.Select(t => t.AsAIFunction()).ToList();
var messages = new List
{
new(ChatRole.System,
"You are a helpful agent. Use the provided tools when needed."),
new(ChatRole.User, userPrompt)
};
int maxSteps = 5;
while (maxSteps-- > 0)
{
// Step A: Ask LLM what to do next
var response = await llm.CompleteAsync(messages,
new ChatOptions { Tools = aiTools });
messages.Add(response.Message);
// Step B: Check if LLM wants to call a tool
var toolCalls = response.Message.Contents
.OfType()
.ToList();
if (toolCalls.Count == 0)
{
// No tool calls — LLM has a final answer
var finalText = response.Message.Contents
.OfType()
.FirstOrDefault()?.Text;
Console.WriteLine($"Final answer: {finalText}");
break;
}
// Step C: Execute each tool call via MCP
foreach (var call in toolCalls)
{
Console.WriteLine($"LLM wants to call: {call.Name}");
// MCP dispatches to the right server + tool
var result = await mcpClient.CallToolAsync(
call.Name,
call.Arguments?.ToDictionary(
kv => kv.Key,
kv => (object?)kv.Value) ?? []);
Console.WriteLine($"MCP result: {result.Content.FirstOrDefault()?.ToString()}");
// Step D: Inject result back into conversation
messages.Add(new ChatMessage(ChatRole.Tool,
result.Content.FirstOrDefault()?.ToString() ?? "")
{
AdditionalProperties = new() { ["toolCallId"] = call.CallId! }
});
}
// Loop back — LLM now sees the tool results and continues
}
}
}
```
Workflows
Now you know what an MCP is. In a workflow you can make a process diagram like thing where MCP agents get connected in a workflow. Between each agent a prompt helps to give context.
In foundy its al visualized and a yml can be used as blueprint of it. In code it looks like this
// Each agent is created with its own MCP tools
var researchAgent = await agentClient.CreateAgentAsync(
model: "gpt-4o",
name: "ResearchAgent",
instructions: "You research topics using web search. " +
"Always cite your sources.",
tools: [ mcpWebSearchTool ] // ← MCP server attached here
);
var writerAgent = await agentClient.CreateAgentAsync(
model: "gpt-4o",
name: "WriterAgent",
instructions: "You write structured reports based on " +
"research provided to you.",
tools: [ mcpDocumentTool ] // ← different MCP server
);
// The workflow thread passes context between agents
var thread = await agentClient.CreateThreadAsync();
// Step 1 — Research agent runs with its MCP tools
await agentClient.CreateMessageAsync(thread.Id,
MessageRole.User, userPrompt);
var researchRun = await agentClient.CreateRunAsync(
thread.Id, researchAgent.Id);
await researchRun.WaitForCompletionAsync();
var researchOutput = await GetLastMessageAsync(thread.Id);
// Step 2 — Writer agent receives research output as input
await agentClient.CreateMessageAsync(thread.Id,
MessageRole.User,
$"Based on this research, write a report:\n\n{researchOutput}");
var writerRun = await agentClient.CreateRunAsync(
thread.Id, writerAgent.Id);
await writerRun.WaitForCompletionAsync();
No comments:
Post a Comment