April 11th, 2025

Azure AI Foundry: Create an MCP Server with Azure AI Agent Service (TypeScript Edition)

Farzad Sunavala
Principal Product Manager

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:

  1. 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.
  2. 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.
  3. 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!

Author

Farzad Sunavala
Principal Product Manager

Building knowledge retrieval capabilities for AI Agents.

0 comments

'; block.insertAdjacentElement('beforebegin', codeheader); let button = codeheader.querySelector('.copy-button'); button.addEventListener("click", async () => { let blockToCopy = block; await copyCode(blockToCopy, button); }); } }); async function copyCode(blockToCopy, button) { let code = blockToCopy.querySelector("code"); let text = ''; if (code) { text = code.innerText; } else { text = blockToCopy.innerText; } try { await navigator.clipboard.writeText(text); } catch (err) { console.error('Failed to copy:', err); } button.innerText = "Copied"; setTimeout(() => { button.innerHTML = '' + svgCodeIcon + ' Copy'; }, 1400); }