Building an MCP Server for Azure AI Agents with TypeScript
TL;DR
This practical guide walks you through creating a Model Context Protocol (MCP) server in TypeScript to connect Azure AI Agents with Claude Desktop or any MCP-compatible client. You’ll learn how to build the server, set up the necessary connections, and handle agent interactions programmatically.
Target Audience
- Are familiar with TypeScript and Azure AI
- Want to integrate Azure AI Agents into desktop applications
- Need to create standardized interfaces for LLM-powered tools
What Problem Does This Solve?
Azure AI Agents provide powerful conversational AI capabilities as part of the Azure AI Foundry ecosystem, but connecting them to desktop applications often requires custom integrations. MCP offers a standardized protocol to bridge these systems, allowing seamless integration between Azure AI Agents and MCP-compatible clients like Claude Desktop.
Prerequisites
Before starting, ensure you have:
- Node.js 16+ installed
- TypeScript setup in your environment
- Claude Desktop or another MCP-compatible client
- Azure CLI configured with appropriate permissions
- Existing Azure AI Agents configured in Azure AI Foundry
Setting Up the TypeScript MCP Server
Step 1: Create a New Project
# Create project directory
mkdir azure-agent-mcp
cd azure-agent-mcp
# Initialize npm project
npm init -y
# Install dependencies
npm install @modelcontextprotocol/sdk zod dotenv @azure/ai-projects @azure/identity
# Install dev dependencies
npm install -D typescript @types/node
Step 2: Configure TypeScript
Create a tsconfig.json file:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"outDir": "./dist",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"ts-node": {
"esm": true
}
}
Step 3: Environment Setup
Create an .env file to store your Azure credentials:
PROJECT_CONNECTION_STRING=your-project-connection-string
DEFAULT_AGENT_ID=your-default-agent-id # Optional
Step 4: Create the MCP Server
Now let’s build the server code. Create a file src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as dotenv from "dotenv";
import { AIProjectsClient } from "@azure/ai-projects";
import { DefaultAzureCredential } from "@azure/identity";
import type { MessageRole, MessageContentOutput } from "@azure/ai-projects";
dotenv.config();
const PROJECT_CONNECTION_STRING = process.env.PROJECT_CONNECTION_STRING;
const DEFAULT_AGENT_ID = process.env.DEFAULT_AGENT_ID || "";
let aiClient: AIProjectsClient | null = null;
Step 5: Implement Core Functions
Let’s add the utility functions that will power our server:
function isTextContent(
content: MessageContentOutput
): content is MessageContentOutput & { type: "text"; text: { value: string } } {
return content.type === "text" && !!(content as any).text?.value;
}
/**
* Initialize the Azure AI Agent client
*/
function initializeServer(): boolean {
if (!PROJECT_CONNECTION_STRING) {
console.error(
"ERROR: Missing required environment variable: PROJECT_CONNECTION_STRING"
);
return false;
}
try {
const credential = new DefaultAzureCredential();
aiClient = AIProjectsClient.fromConnectionString(
PROJECT_CONNECTION_STRING,
credential
);
return true;
} catch (error) {
console.error(
`ERROR: Failed to initialize AIProjectClient: ${
error instanceof Error ? error.message : String(error)
}`
);
return false;
}
}
function checkServerInitialized() {
if (!serverInitialized) {
return {
content: [
{
type: "text" as const,
text: "Error: Azure AI Agent server is not initialized. Check server logs for details.",
},
],
};
}
return null;
}
Step 6 Implement the Query Agent Function
This core function handles communication with Azure AI Agents:
/**
* Query an Azure AI Agent and get the response
*/
async function queryAgent(
agentId: string,
userQuery: string,
existingThreadId?: string
): Promise<{ response: string; threadId: string }> {
if (!aiClient) {
throw new Error("AI client not initialized");
}
try {
// Verify agent exists and is accessible
await aiClient.agents.getAgent(agentId);
// Create a new thread or use existing one
let threadId = existingThreadId;
if (!threadId) {
const thread = await aiClient.agents.createThread();
threadId = thread.id;
}
// Add message to thread
await aiClient.agents.createMessage(threadId, {
role: "user" as MessageRole,
content: userQuery,
});
// Create and process the run
let run = await aiClient.agents.createRun(threadId, agentId);
// Poll until the run is complete
while (["queued", "in_progress", "requires_action"].includes(run.status)) {
await new Promise((resolve) => setTimeout(resolve, 1000)); // Non-blocking sleep
run = await aiClient.agents.getRun(threadId, run.id);
}
if (run.status === "failed") {
return {
response: `Error: Agent run failed: ${
run.lastError?.message || "Unknown error"
}`,
threadId,
};
}
// Get the agent's response
const messages = await aiClient.agents.listMessages(threadId);
const assistantMessages = messages.data.filter(
(m) => m.role === "assistant"
);
const lastMessage = assistantMessages[assistantMessages.length - 1];
let responseText = "";
if (lastMessage) {
for (const content of lastMessage.content) {
if (isTextContent(content)) {
responseText += content.text.value + "\n";
}
}
}
return { response: responseText.trim(), threadId };
} catch (error) {
throw new Error(
`Agent query failed: ${
error instanceof Error ? error.message : String(error)
}`
);
}
}
Step 7: Register MCP Tools
Now, let’s create the MCP server and register the tools:
// Initialize server
const serverInitialized = initializeServer();
// Create MCP server
const mcp = new McpServer({
name: "azure-agent",
version: "1.0.0",
description: "MCP server for Azure AI Agent Service integration",
});
// Register tools
mcp.tool(
"query_agent",
"Query a specific Azure AI Agent",
{
agent_id: z.string().describe("The ID of the Azure AI Agent to query"),
query: z.string().describe("The question or request to send to the agent"),
thread_id: z
.string()
.optional()
.describe("Thread ID for conversation continuation"),
},
async ({ agent_id, query, thread_id }) => {
const errorResponse = checkServerInitialized();
if (errorResponse) return errorResponse;
try {
const { response, threadId } = await queryAgent(
agent_id,
query,
thread_id
);
return {
content: [
{
type: "text" as const,
text: `## Response from Azure AI Agent\n\n${response}\n\n(thread_id: ${threadId})`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text" as const,
text: `Error querying agent: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
}
);
Step 8: Add Default Agent Query Tool
mcp.tool(
"query_default_agent",
"Query the default Azure AI Agent",
{
query: z.string().describe("The question or request to send to the agent"),
thread_id: z
.string()
.optional()
.describe("Thread ID for conversation continuation"),
},
async ({ query, thread_id }) => {
const errorResponse = checkServerInitialized();
if (errorResponse) return errorResponse;
if (!DEFAULT_AGENT_ID) {
return {
content: [
{
type: "text" as const,
text: "Error: No default agent configured. Set DEFAULT_AGENT_ID environment variable or use query_agent tool.",
},
],
};
}
try {
const { response, threadId } = await queryAgent(
DEFAULT_AGENT_ID,
query,
thread_id
);
return {
content: [
{
type: "text" as const,
text: `## Response from Default Azure AI Agent\n\n${response}\n\n(thread_id: ${threadId})`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text" as const,
text: `Error querying default agent: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
}
);
Step 9: Add List Agents Tool
mcp.tool("list_agents", "List all available Azure AI Agents", {}, async () => {
const errorResponse = checkServerInitialized();
if (errorResponse) return errorResponse;
try {
const agents = await aiClient!.agents.listAgents();
if (!agents.data || agents.data.length === 0) {
return {
content: [
{
type: "text" as const,
text: "No agents found in the Azure AI Agent Service.",
},
],
};
}
let result = "## Available Azure AI Agents\n\n";
for (const agent of agents.data) {
result += `- **${agent.name}** (ID: \`${agent.id}\`)\n`;
}
if (DEFAULT_AGENT_ID) {
result += `\n**Default Agent ID**: \`${DEFAULT_AGENT_ID}\``;
}
return {
content: [{ type: "text" as const, text: result }],
};
} catch (error) {
return {
content: [
{
type: "text" as const,
text: `Error listing agents: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
});
Step 10: Create the Main Function
async function main() {
console.error("\n==================================================");
console.error(
`Azure AI Agent MCP Server ${
serverInitialized ? "successfully initialized" : "initialization failed"
}`
);
console.error("Starting server...");
console.error("==================================================\n");
const transport = new StdioServerTransport();
await mcp.connect(transport);
}
main().catch((error) => {
console.error(
`FATAL: ${error instanceof Error ? error.message : String(error)}`
);
process.exit(1);
});
Step 11: Build and Run the Server
Add the following to your package.json:
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
Build and run the server:
npm run build
npm start
Integrating with Claude Desktop
To integrate your TypeScript MCP server with Claude Desktop, you’ll need to update the Claude Desktop configuration file (claude_desktop_config.json):
{
"mcpServers": {
"azure-agent": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/azure-agent-mcp/dist/index.js"],
"env": {
"PROJECT_CONNECTION_STRING": "your-project-connection-string",
"DEFAULT_AGENT_ID": "your-default-agent-id"
}
}
}
}
This configuration tells Claude Desktop:
- There’s an MCP server named “azure-agent”
- It should be launched by running the specified Node.js script
- The necessary environment variables should be passed to the process
Practical Usage Examples
Once your MCP server is integrated with Claude Desktop, you can use it in various ways:
- Querying a Specific Agent
Ask Claude Desktop: “Can you use the azure-agent to check if there are weather alerts in Seattle?”
Behind the scenes, Claude will use the
query_agent
tool with the appropriate agent ID. - Using the Default Agent
Ask Claude Desktop: “Can you use the default azure agent to summarize the latest NBA news?”
Claude will automatically use the
query_default_agent
tool for this request. - Discovering Available Agents
Ask Claude Desktop: “What Azure AI agents are available to me?”
Claude will call the
list_agents
tool and display the results.
Conclusion
By building an MCP server for Azure AI Agents with TypeScript, you can create a powerful bridge between Azure AI Foundry services and MCP hosts such as Claude Desktop. This sample leverages the strengths of both systems while maintaining a clean separation of concerns through the standardized MCP protocol.
Happy coding!
0 comments
Be the first to start the discussion.