{"id":197,"date":"2025-04-11T11:59:00","date_gmt":"2025-04-11T18:59:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/foundry\/?p=197"},"modified":"2025-04-21T13:57:20","modified_gmt":"2025-04-21T20:57:20","slug":"integrating-azure-ai-agents-mcp-typescript","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/foundry\/integrating-azure-ai-agents-mcp-typescript\/","title":{"rendered":"Azure AI Foundry: Create an MCP Server with Azure AI Agent Service (TypeScript Edition)"},"content":{"rendered":"<h1>Building an MCP Server for Azure AI Agents with TypeScript<\/h1>\n<h2>TL;DR<\/h2>\n<p>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&#8217;ll learn how to build the server, set up the necessary connections, and handle agent interactions programmatically.<\/p>\n<h2>Target Audience<\/h2>\n<ul>\n<li>Are familiar with TypeScript and Azure AI<\/li>\n<li>Want to integrate Azure AI Agents into desktop applications<\/li>\n<li>Need to create standardized interfaces for LLM-powered tools<\/li>\n<\/ul>\n<h2>What Problem Does This Solve?<\/h2>\n<p>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.<\/p>\n<h2>Prerequisites<\/h2>\n<p>Before starting, ensure you have:<\/p>\n<ul>\n<li>Node.js 16+ installed<\/li>\n<li>TypeScript setup in your environment<\/li>\n<li>Claude Desktop or another MCP-compatible client<\/li>\n<li>Azure CLI configured with appropriate permissions<\/li>\n<li>Existing Azure AI Agents configured in Azure AI Foundry<\/li>\n<\/ul>\n<h2>Setting Up the TypeScript MCP Server<\/h2>\n<h3>Step 1: Create a New Project<\/h3>\n<pre><code class=\"language-bash\"># Create project directory\nmkdir azure-agent-mcp\ncd azure-agent-mcp\n\n# Initialize npm project\nnpm init -y\n\n# Install dependencies\nnpm install @modelcontextprotocol\/sdk zod dotenv @azure\/ai-projects @azure\/identity\n\n# Install dev dependencies\nnpm install -D typescript @types\/node<\/code><\/pre>\n<h3>Step 2: Configure TypeScript<\/h3>\n<p>Create a tsconfig.json file:<\/p>\n<pre><code class=\"language-json\">{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"outDir\": \".\/dist\",\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"include\": [\"src\/**\/*\"],\n  \"ts-node\": {\n    \"esm\": true\n  }\n}<\/code><\/pre>\n<h3>Step 3: Environment Setup<\/h3>\n<p>Create an .env file to store your Azure credentials:<\/p>\n<pre><code class=\"language-bash\">PROJECT_CONNECTION_STRING=your-project-connection-string\nDEFAULT_AGENT_ID=your-default-agent-id  # Optional<\/code><\/pre>\n<h3>Step 4: Create the MCP Server<\/h3>\n<p>Now let&#8217;s build the server code. Create a file src\/index.ts:<\/p>\n<pre><code class=\"language-typescript\">import { McpServer } from \"@modelcontextprotocol\/sdk\/server\/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol\/sdk\/server\/stdio.js\";\nimport { z } from \"zod\";\nimport * as dotenv from \"dotenv\";\nimport { AIProjectsClient } from \"@azure\/ai-projects\";\nimport { DefaultAzureCredential } from \"@azure\/identity\";\nimport type { MessageRole, MessageContentOutput } from \"@azure\/ai-projects\";\n\ndotenv.config();\n\nconst PROJECT_CONNECTION_STRING = process.env.PROJECT_CONNECTION_STRING;\nconst DEFAULT_AGENT_ID = process.env.DEFAULT_AGENT_ID || \"\";\n\nlet aiClient: AIProjectsClient | null = null;<\/code><\/pre>\n<h3>Step 5: Implement Core Functions<\/h3>\n<p>Let&#8217;s add the utility functions that will power our server:<\/p>\n<pre><code class=\"language-typescript\">function isTextContent(\n  content: MessageContentOutput\n): content is MessageContentOutput &amp; { type: \"text\"; text: { value: string } } {\n  return content.type === \"text\" &amp;&amp; !!(content as any).text?.value;\n}\n\n\/**\n * Initialize the Azure AI Agent client\n *\/\nfunction initializeServer(): boolean {\n  if (!PROJECT_CONNECTION_STRING) {\n    console.error(\n      \"ERROR: Missing required environment variable: PROJECT_CONNECTION_STRING\"\n    );\n    return false;\n  }\n\n  try {\n    const credential = new DefaultAzureCredential();\n    aiClient = AIProjectsClient.fromConnectionString(\n      PROJECT_CONNECTION_STRING,\n      credential\n    );\n    return true;\n  } catch (error) {\n    console.error(\n      `ERROR: Failed to initialize AIProjectClient: ${\n        error instanceof Error ? error.message : String(error)\n      }`\n    );\n    return false;\n  }\n}\n\nfunction checkServerInitialized() {\n  if (!serverInitialized) {\n    return {\n      content: [\n        {\n          type: \"text\" as const,\n          text: \"Error: Azure AI Agent server is not initialized. Check server logs for details.\",\n        },\n      ],\n    };\n  }\n  return null;\n}<\/code><\/pre>\n<h3>Step 6 Implement the Query Agent Function<\/h3>\n<p>This core function handles communication with Azure AI Agents:<\/p>\n<pre><code class=\"language-typescript\">\/**\n * Query an Azure AI Agent and get the response\n *\/\nasync function queryAgent(\n  agentId: string,\n  userQuery: string,\n  existingThreadId?: string\n): Promise&lt;{ response: string; threadId: string }&gt; {\n  if (!aiClient) {\n    throw new Error(\"AI client not initialized\");\n  }\n\n  try {\n    \/\/ Verify agent exists and is accessible\n    await aiClient.agents.getAgent(agentId);\n\n    \/\/ Create a new thread or use existing one\n    let threadId = existingThreadId;\n    if (!threadId) {\n      const thread = await aiClient.agents.createThread();\n      threadId = thread.id;\n    }\n\n    \/\/ Add message to thread\n    await aiClient.agents.createMessage(threadId, {\n      role: \"user\" as MessageRole,\n      content: userQuery,\n    });\n\n    \/\/ Create and process the run\n    let run = await aiClient.agents.createRun(threadId, agentId);\n\n    \/\/ Poll until the run is complete\n    while ([\"queued\", \"in_progress\", \"requires_action\"].includes(run.status)) {\n      await new Promise((resolve) =&gt; setTimeout(resolve, 1000)); \/\/ Non-blocking sleep\n      run = await aiClient.agents.getRun(threadId, run.id);\n    }\n\n    if (run.status === \"failed\") {\n      return {\n        response: `Error: Agent run failed: ${\n          run.lastError?.message || \"Unknown error\"\n        }`,\n        threadId,\n      };\n    }\n\n    \/\/ Get the agent's response\n    const messages = await aiClient.agents.listMessages(threadId);\n    const assistantMessages = messages.data.filter(\n      (m) =&gt; m.role === \"assistant\"\n    );\n    const lastMessage = assistantMessages[assistantMessages.length - 1];\n\n    let responseText = \"\";\n    if (lastMessage) {\n      for (const content of lastMessage.content) {\n        if (isTextContent(content)) {\n          responseText += content.text.value + \"\\n\";\n        }\n      }\n    }\n\n    return { response: responseText.trim(), threadId };\n  } catch (error) {\n    throw new Error(\n      `Agent query failed: ${\n        error instanceof Error ? error.message : String(error)\n      }`\n    );\n  }\n}<\/code><\/pre>\n<h3>Step 7: Register MCP Tools<\/h3>\n<p>Now, let&#8217;s create the MCP server and register the tools:<\/p>\n<pre><code class=\"language-typescript\">\/\/ Initialize server\nconst serverInitialized = initializeServer();\n\n\/\/ Create MCP server\nconst mcp = new McpServer({\n  name: \"azure-agent\",\n  version: \"1.0.0\",\n  description: \"MCP server for Azure AI Agent Service integration\",\n});\n\n\/\/ Register tools\nmcp.tool(\n  \"query_agent\",\n  \"Query a specific Azure AI Agent\",\n  {\n    agent_id: z.string().describe(\"The ID of the Azure AI Agent to query\"),\n    query: z.string().describe(\"The question or request to send to the agent\"),\n    thread_id: z\n      .string()\n      .optional()\n      .describe(\"Thread ID for conversation continuation\"),\n  },\n  async ({ agent_id, query, thread_id }) =&gt; {\n    const errorResponse = checkServerInitialized();\n    if (errorResponse) return errorResponse;\n\n    try {\n      const { response, threadId } = await queryAgent(\n        agent_id,\n        query,\n        thread_id\n      );\n\n      return {\n        content: [\n          {\n            type: \"text\" as const,\n            text: `## Response from Azure AI Agent\\n\\n${response}\\n\\n(thread_id: ${threadId})`,\n          },\n        ],\n      };\n    } catch (error) {\n      return {\n        content: [\n          {\n            type: \"text\" as const,\n            text: `Error querying agent: ${\n              error instanceof Error ? error.message : String(error)\n            }`,\n          },\n        ],\n      };\n    }\n  }\n);<\/code><\/pre>\n<h3>Step 8: Add Default Agent Query Tool<\/h3>\n<pre><code class=\"language-typescript\">mcp.tool(\n  \"query_default_agent\",\n  \"Query the default Azure AI Agent\",\n  {\n    query: z.string().describe(\"The question or request to send to the agent\"),\n    thread_id: z\n      .string()\n      .optional()\n      .describe(\"Thread ID for conversation continuation\"),\n  },\n  async ({ query, thread_id }) =&gt; {\n    const errorResponse = checkServerInitialized();\n    if (errorResponse) return errorResponse;\n\n    if (!DEFAULT_AGENT_ID) {\n      return {\n        content: [\n          {\n            type: \"text\" as const,\n            text: \"Error: No default agent configured. Set DEFAULT_AGENT_ID environment variable or use query_agent tool.\",\n          },\n        ],\n      };\n    }\n\n    try {\n      const { response, threadId } = await queryAgent(\n        DEFAULT_AGENT_ID,\n        query,\n        thread_id\n      );\n\n      return {\n        content: [\n          {\n            type: \"text\" as const,\n            text: `## Response from Default Azure AI Agent\\n\\n${response}\\n\\n(thread_id: ${threadId})`,\n          },\n        ],\n      };\n    } catch (error) {\n      return {\n        content: [\n          {\n            type: \"text\" as const,\n            text: `Error querying default agent: ${\n              error instanceof Error ? error.message : String(error)\n            }`,\n          },\n        ],\n      };\n    }\n  }\n);<\/code><\/pre>\n<h3>Step 9: Add List Agents Tool<\/h3>\n<pre><code class=\"language-typescript\">mcp.tool(\"list_agents\", \"List all available Azure AI Agents\", {}, async () =&gt; {\n  const errorResponse = checkServerInitialized();\n  if (errorResponse) return errorResponse;\n\n  try {\n    const agents = await aiClient!.agents.listAgents();\n    if (!agents.data || agents.data.length === 0) {\n      return {\n        content: [\n          {\n            type: \"text\" as const,\n            text: \"No agents found in the Azure AI Agent Service.\",\n          },\n        ],\n      };\n    }\n\n    let result = \"## Available Azure AI Agents\\n\\n\";\n    for (const agent of agents.data) {\n      result += `- **${agent.name}** (ID: \\`${agent.id}\\`)\\n`;\n    }\n\n    if (DEFAULT_AGENT_ID) {\n      result += `\\n**Default Agent ID**: \\`${DEFAULT_AGENT_ID}\\``;\n    }\n\n    return {\n      content: [{ type: \"text\" as const, text: result }],\n    };\n  } catch (error) {\n    return {\n      content: [\n        {\n          type: \"text\" as const,\n          text: `Error listing agents: ${\n            error instanceof Error ? error.message : String(error)\n          }`,\n        },\n      ],\n    };\n  }\n});<\/code><\/pre>\n<h3>Step 10: Create the Main Function<\/h3>\n<pre><code class=\"language-typescript\">async function main() {\n  console.error(\"\\n==================================================\");\n  console.error(\n    `Azure AI Agent MCP Server ${\n      serverInitialized ? \"successfully initialized\" : \"initialization failed\"\n    }`\n  );\n  console.error(\"Starting server...\");\n  console.error(\"==================================================\\n\");\n\n  const transport = new StdioServerTransport();\n  await mcp.connect(transport);\n}\n\nmain().catch((error) =&gt; {\n  console.error(\n    `FATAL: ${error instanceof Error ? error.message : String(error)}`\n  );\n  process.exit(1);\n});<\/code><\/pre>\n<h3>Step 11: Build and Run the Server<\/h3>\n<p>Add the following to your package.json:<\/p>\n<pre><code class=\"language-json\">{\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"start\": \"node dist\/index.js\"\n  }\n}<\/code><\/pre>\n<p>Build and run the server:<\/p>\n<pre><code class=\"language-bash\">npm run build\nnpm start<\/code><\/pre>\n<h2>Integrating with Claude Desktop<\/h2>\n<p>To integrate your TypeScript MCP server with Claude Desktop, you&#8217;ll need to update the Claude Desktop configuration file (claude_desktop_config.json):<\/p>\n<pre><code class=\"language-json\">{\n  \"mcpServers\": {\n    \"azure-agent\": {\n      \"command\": \"node\",\n      \"args\": [\"\/ABSOLUTE\/PATH\/TO\/azure-agent-mcp\/dist\/index.js\"],\n      \"env\": {\n        \"PROJECT_CONNECTION_STRING\": \"your-project-connection-string\",\n        \"DEFAULT_AGENT_ID\": \"your-default-agent-id\"\n      }\n    }\n  }\n}<\/code><\/pre>\n<p>This configuration tells Claude Desktop:<\/p>\n<ul>\n<li>There&#8217;s an MCP server named &#8220;azure-agent&#8221;<\/li>\n<li>It should be launched by running the specified Node.js script<\/li>\n<li>The necessary environment variables should be passed to the process<\/li>\n<\/ul>\n<h2>Practical Usage Examples<\/h2>\n<p>Once your MCP server is integrated with Claude Desktop, you can use it in various ways:<\/p>\n<ol>\n<li>\n<p>Querying a Specific Agent\nAsk Claude Desktop: &#8220;Can you use the azure-agent to check if there are weather alerts in Seattle?&#8221;\nBehind the scenes, Claude will use the <code>query_agent<\/code> tool with the appropriate agent ID.<\/p>\n<\/li>\n<li>\n<p>Using the Default Agent\nAsk Claude Desktop: &#8220;Can you use the default azure agent to summarize the latest NBA news?&#8221;\nClaude will automatically use the <code>query_default_agent<\/code> tool for this request.<\/p>\n<\/li>\n<li>\n<p>Discovering Available Agents\nAsk Claude Desktop: &#8220;What Azure AI agents are available to me?&#8221;\nClaude will call the <code>list_agents<\/code> tool and display the results.<\/p>\n<\/li>\n<\/ol>\n<h2>Conclusion<\/h2>\n<p>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.<\/p>\n<p>Happy coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to integrate Azure AI Foundry&#8217;s Agent Service with Claude Desktop using the Model Context Protocol (MCP) for powerful integration for MCP clients.<\/p>\n","protected":false},"author":170596,"featured_media":206,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[10,6,7,11,9,8,18],"class_list":["post-197","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-microsoft-foundry","tag-ai-agents","tag-azure-ai","tag-claude","tag-integration","tag-mcp","tag-model-context-protocol","tag-typescript"],"acf":[],"blog_post_summary":"<p>Learn how to integrate Azure AI Foundry&#8217;s Agent Service with Claude Desktop using the Model Context Protocol (MCP) for powerful integration for MCP clients.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/posts\/197","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/users\/170596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/comments?post=197"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/posts\/197\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/media\/206"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/media?parent=197"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/categories?post=197"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/tags?post=197"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}