<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://laveeshb.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://laveeshb.com/" rel="alternate" type="text/html" /><updated>2026-08-05T10:50:43+00:00</updated><id>https://laveeshb.com/feed.xml</id><title type="html">Laveesh Bansal</title><subtitle>Software Architect at GitHub, leading GitHub Enterprise Cloud Data Residency</subtitle><author><name>Laveesh Bansal</name><email></email></author><entry><title type="html">kernel-internals.org: Learning the Why Behind the Linux Kernel</title><link href="https://laveeshb.com/blog/2026/07/kernel-internals-org/" rel="alternate" type="text/html" title="kernel-internals.org: Learning the Why Behind the Linux Kernel" /><published>2026-07-26T00:00:00+00:00</published><updated>2026-07-26T00:00:00+00:00</updated><id>https://laveeshb.com/blog/2026/07/kernel-internals-org</id><content type="html" xml:base="https://laveeshb.com/blog/2026/07/kernel-internals-org/"><![CDATA[<p>Let me calibrate expectations first: I’m new to kernel development. Getting into it recently made me realize how much of the kernel I’d been depending on without really understanding — and that’s a big part of why I started <a href="https://kernel-internals.org" target="_blank">kernel-internals.org</a>. It’s where I work through the <em>why</em> behind the kernel’s design as I dig into it, and organize it so the next curious person doesn’t have to start from scratch.</p>

<p>So think of the site less as an authoritative reference and more as careful notes, taken in public. This post is about why I started it and where it stands.</p>

<h2 id="the-gap-i-kept-running-into">The gap I kept running into</h2>

<p>When I started reading, the material felt oddly polarized. <a href="https://docs.kernel.org" target="_blank">docs.kernel.org</a> is authoritative but tells you <em>what</em> and <em>how</em> — it’s reference documentation, not a narrative. <a href="https://lwn.net" target="_blank">LWN</a> is the gold standard for the <em>why</em>, but it’s news-shaped — tied to a particular patch series — and not organized as something you can sit down and browse by subsystem.</p>

<p>What I kept wishing for was a current, organized place that explained the <em>reasoning</em>: the trade-offs, the alternatives that got rejected, the mailing-list arguments that produced the design we ended up with. I couldn’t find it, so I started assembling it as I learned. It’s meant to complement <a href="https://docs.kernel.org" target="_blank">docs.kernel.org</a>, not compete with it.</p>

<h2 id="where-it-stands-today">Where it stands today</h2>

<p>Today it spans roughly 375 pages across 28 subsystems, built as a static MkDocs site. Memory management is the deepest part (the page allocator, reclaim, folios, huge pages, <code class="language-plaintext highlighter-rouge">get_user_pages</code>, the kernel’s address-space layout), with broader coverage of the scheduler, networking, the architecture layer, io_uring, locking, filesystems, and more.</p>

<p>Two principles I try to hold to — precisely <em>because</em> I’m not the expert in the room:</p>

<ul>
  <li><strong>Every claim should trace to a primary source</strong> — a specific commit on <a href="https://git.kernel.org" target="_blank">git.kernel.org</a>, an <a href="https://lwn.net" target="_blank">LWN</a> article, a <a href="https://lore.kernel.org" target="_blank">lore.kernel.org</a> thread. The authority lives in those sources, not in me. If I can’t cite it, it doesn’t belong on the site, and where I’ve summarized something the original is one click away.</li>
  <li><strong>The cross-references stay honest</strong> — there’s CI that fails the build on any broken internal link or anchor, so as the site grows the links between pages don’t quietly rot.</li>
</ul>

<h2 id="what-i-try-to-do-on-a-page">What I try to do on a page</h2>

<p>The goal isn’t to list the right structs and functions — <a href="https://docs.kernel.org" target="_blank">docs.kernel.org</a> and the source already do that better than I could. What I aim for is an honest attempt at <em>why</em>. Take <a href="https://kernel-internals.org/mm/gup/" target="_blank"><code>get_user_pages</code></a>: the part I found worth understanding wasn’t the function signature, it was why the kernel ended up with two nearly-identical APIs, why an ordinary reference count couldn’t express “this page is being used for DMA,” and how that ambiguity led to a long tail of subtle bugs. I don’t always get this right, and some pages are thinner or shakier than others.</p>

<h2 id="where-its-going">Where it’s going</h2>

<p>It’s open source, and there’s a lot of surface left to cover — scheduler extensions, Rust in the kernel, the mainlining of PREEMPT_RT, and plenty of subsystems that are still thin. I’ll keep adding to it as I learn.</p>

<p>If any of this is your kind of thing, it’s all at <a href="https://kernel-internals.org" target="_blank">kernel-internals.org</a>.</p>]]></content><author><name>Laveesh Bansal</name></author><category term="linux" /><category term="linux-kernel" /><category term="documentation" /><category term="open-source" /><category term="learning-in-public" /><summary type="html"><![CDATA[Making sense of why the Linux kernel is built the way it is. I'm new to kernel development, and working through the why is a big part of how I'm learning — everything traced to primary sources.]]></summary></entry><entry><title type="html">What’s New in Logic Apps MCP v0.3: Write Operations &amp;amp; Knowledge Tools</title><link href="https://laveeshb.com/blog/2026/01/logic-apps-mcp-write-operations/" rel="alternate" type="text/html" title="What’s New in Logic Apps MCP v0.3: Write Operations &amp;amp; Knowledge Tools" /><published>2026-01-02T00:00:00+00:00</published><updated>2026-01-02T00:00:00+00:00</updated><id>https://laveeshb.com/blog/2026/01/logic-apps-mcp-write-operations</id><content type="html" xml:base="https://laveeshb.com/blog/2026/01/logic-apps-mcp-write-operations/"><![CDATA[<p>I just shipped v0.3.0 of the <a href="https://github.com/laveeshb/logicapps-mcp" target="_blank">Logic Apps MCP server</a> with 14 new tools. The previous releases focused on reading and debugging workflows. This update adds write operations — you can now create, modify, and manage workflows through your AI assistant.</p>

<p>Here’s what’s new.</p>

<p><strong>Contents:</strong></p>
<ul>
  <li><a href="#write-operations">Write Operations</a></li>
  <li><a href="#workflow-lifecycle-management">Workflow Lifecycle Management</a></li>
  <li><a href="#connector-management">Connector Management</a></li>
  <li><a href="#knowledge-tools">Knowledge Tools</a></li>
  <li><a href="#performance-improvements">Performance Improvements</a></li>
  <li><a href="#updated-tool-summary">Updated Tool Summary</a></li>
</ul>

<h2 id="write-operations">Write Operations</h2>

<p>The biggest change in v0.3 is the shift from read-only to read-write. Three new tools let you manage workflow definitions:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">create_workflow</code></strong> — Creates a new workflow. For Consumption SKU, this creates a new Logic App resource. For Standard SKU, it adds a workflow to an existing Logic App.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">update_workflow</code></strong> — Modifies an existing workflow’s definition. Handles the API differences between SKUs automatically.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">delete_workflow</code></strong> — Removes a workflow entirely. Use with caution.</li>
</ul>

<p>Now you can ask your AI assistant things like:</p>

<ul>
  <li><em>“Create a workflow that triggers on HTTP request and sends an email”</em></li>
  <li><em>“Add a condition to check if the order total exceeds $1000”</em></li>
  <li><em>“Update the workflow to retry failed API calls”</em></li>
</ul>

<p>The AI can build the workflow definition and deploy it directly.</p>

<h2 id="workflow-lifecycle-management">Workflow Lifecycle Management</h2>

<p>v0.3 adds two new tools for controlling workflow state, complementing the existing <code class="language-plaintext highlighter-rouge">run_trigger</code> and <code class="language-plaintext highlighter-rouge">cancel_run</code>:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">enable_workflow</code></strong> — Enables a workflow that was previously disabled.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">disable_workflow</code></strong> — Disables a workflow without deleting it. Useful for maintenance or temporarily stopping processing.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">run_trigger</code></strong> — Manually fires a workflow trigger for testing. No need to wait for a schedule or craft an HTTP request.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">cancel_run</code></strong> — Cancels a workflow run that’s in <code class="language-plaintext highlighter-rouge">Running</code> or <code class="language-plaintext highlighter-rouge">Waiting</code> status. Handy when a workflow is stuck or you need to stop a long-running process.</li>
</ul>

<p>Example prompts:</p>

<ul>
  <li><em>“Disable the order-processing workflow while I update the database”</em></li>
  <li><em>“Run the daily-report workflow now so I can test it”</em></li>
  <li><em>“Cancel all running instances of the batch-import workflow”</em></li>
</ul>

<h2 id="connector-management">Connector Management</h2>

<p>Logic Apps connect to external services through API connections. v0.3 adds tools to work with connectors programmatically:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">create_connection</code></strong> — Creates a new API connection for managed connectors like SQL Server, Service Bus, or SharePoint. Supports both OAuth and parameter-based authentication.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">get_connector_swagger</code></strong> — Fetches the OpenAPI spec for any managed connector, so the AI understands what operations and parameters are available.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">invoke_connector_operation</code></strong> — Calls dynamic connector operations like <code class="language-plaintext highlighter-rouge">GetTables</code>, <code class="language-plaintext highlighter-rouge">GetQueues</code>, or <code class="language-plaintext highlighter-rouge">GetSchemas</code> through an existing connection.</li>
</ul>

<p>This enables conversational flows like:</p>

<ul>
  <li><em>“Connect to my SQL database and show me the available tables”</em></li>
  <li><em>“What operations does the Service Bus connector support?”</em></li>
  <li><em>“List all queues in my Service Bus namespace”</em></li>
</ul>

<p>The AI can discover schemas and structures, then use that information to build workflows that reference the right tables and fields.</p>

<h2 id="knowledge-tools">Knowledge Tools</h2>

<p>One challenge with AI assistants is they don’t always know the specifics of Logic Apps — expression syntax, connector quirks, SKU differences. v0.3 bundles documentation directly into the server through four knowledge tools:</p>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_troubleshooting_guide</code></td>
      <td>Common errors and how to fix them — expression failures, connection issues, run errors</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_authoring_guide</code></td>
      <td>Best practices for workflow design, connector usage, and deployment patterns</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_reference</code></td>
      <td>Tool catalog and SKU differences between Consumption and Standard</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_workflow_instructions</code></td>
      <td>Step-by-step guides for common tasks like diagnosing failures or creating workflows</td>
    </tr>
  </tbody>
</table>

<p>The guides are markdown files in the <a href="https://github.com/laveeshb/logicapps-mcp/tree/main/knowledge" target="_blank">knowledge folder</a>. When you install the MCP server, these files are bundled with it — no separate download needed. When you ask <em>“Why is my expression failing?”</em>, the AI pulls the relevant troubleshooting docs and gives you targeted advice instead of generic suggestions.</p>

<h2 id="performance-improvements">Performance Improvements</h2>

<p>v0.3 includes several reliability improvements:</p>

<ul>
  <li><strong>Retry with exponential backoff</strong> — Transient failures (429s, 5xx errors) are automatically retried with jitter to avoid thundering herd issues.</li>
  <li><strong>SKU detection caching</strong> — The server caches whether a Logic App is Consumption or Standard, avoiding repeated API calls.</li>
  <li><strong>Parallelized operations</strong> — <code class="language-plaintext highlighter-rouge">get_connector_swagger</code> fetches connector metadata in parallel, reducing response time.</li>
  <li><strong>Content link limits</strong> — Fetching action inputs/outputs has a 30-second timeout and 10MB size limit to prevent hanging on large payloads.</li>
</ul>

<h2 id="updated-tool-summary">Updated Tool Summary</h2>

<p>The server now has 37 tools:</p>

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th>Tools</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Discovery</td>
      <td><code class="language-plaintext highlighter-rouge">list_subscriptions</code>, <code class="language-plaintext highlighter-rouge">list_logic_apps</code>, <code class="language-plaintext highlighter-rouge">list_workflows</code></td>
    </tr>
    <tr>
      <td>Definitions</td>
      <td><code class="language-plaintext highlighter-rouge">get_workflow_definition</code>, <code class="language-plaintext highlighter-rouge">get_workflow_swagger</code>, <code class="language-plaintext highlighter-rouge">list_workflow_versions</code>, <code class="language-plaintext highlighter-rouge">get_workflow_version</code></td>
    </tr>
    <tr>
      <td>Triggers</td>
      <td><code class="language-plaintext highlighter-rouge">get_workflow_triggers</code>, <code class="language-plaintext highlighter-rouge">get_trigger_history</code>, <code class="language-plaintext highlighter-rouge">get_trigger_callback_url</code></td>
    </tr>
    <tr>
      <td>Run History</td>
      <td><code class="language-plaintext highlighter-rouge">list_run_history</code>, <code class="language-plaintext highlighter-rouge">get_run_details</code>, <code class="language-plaintext highlighter-rouge">get_run_actions</code>, <code class="language-plaintext highlighter-rouge">search_runs</code></td>
    </tr>
    <tr>
      <td>Debugging</td>
      <td><code class="language-plaintext highlighter-rouge">get_action_io</code>, <code class="language-plaintext highlighter-rouge">get_action_repetitions</code>, <code class="language-plaintext highlighter-rouge">get_scope_repetitions</code>, <code class="language-plaintext highlighter-rouge">get_action_request_history</code>, <code class="language-plaintext highlighter-rouge">get_expression_traces</code></td>
    </tr>
    <tr>
      <td>Connections</td>
      <td><code class="language-plaintext highlighter-rouge">get_connections</code>, <code class="language-plaintext highlighter-rouge">get_connection_details</code>, <code class="language-plaintext highlighter-rouge">test_connection</code>, <code class="language-plaintext highlighter-rouge">create_connection</code>, <code class="language-plaintext highlighter-rouge">get_connector_swagger</code>, <code class="language-plaintext highlighter-rouge">invoke_connector_operation</code></td>
    </tr>
    <tr>
      <td>Diagnostics</td>
      <td><code class="language-plaintext highlighter-rouge">get_host_status</code></td>
    </tr>
    <tr>
      <td>Write Operations</td>
      <td><code class="language-plaintext highlighter-rouge">enable_workflow</code>, <code class="language-plaintext highlighter-rouge">disable_workflow</code>, <code class="language-plaintext highlighter-rouge">run_trigger</code>, <code class="language-plaintext highlighter-rouge">cancel_run</code>, <code class="language-plaintext highlighter-rouge">create_workflow</code>, <code class="language-plaintext highlighter-rouge">update_workflow</code>, <code class="language-plaintext highlighter-rouge">delete_workflow</code></td>
    </tr>
    <tr>
      <td>Knowledge</td>
      <td><code class="language-plaintext highlighter-rouge">get_troubleshooting_guide</code>, <code class="language-plaintext highlighter-rouge">get_authoring_guide</code>, <code class="language-plaintext highlighter-rouge">get_reference</code>, <code class="language-plaintext highlighter-rouge">get_workflow_instructions</code></td>
    </tr>
  </tbody>
</table>

<p>The shift to write operations opens up new possibilities. Your AI assistant can now help you not just understand and debug workflows, but build and manage them. Combined with the knowledge tools, it has the context to make better suggestions.</p>

<p>If you’re already using the server, update with <code class="language-plaintext highlighter-rouge">npm update -g @laveeshb/logicapps-mcp</code>. If not, check out the <a href="https://github.com/laveeshb/logicapps-mcp" target="_blank">repo</a> to get started.</p>]]></content><author><name>Laveesh Bansal</name></author><category term="azure" /><category term="mcp" /><category term="logic-apps" /><category term="typescript" /><category term="ai" /><summary type="html"><![CDATA[The Logic Apps MCP server goes read-write with workflow management, connector creation, and built-in knowledge for AI assistants.]]></summary></entry><entry><title type="html">Python Support for Azure Functions SQS Extension</title><link href="https://laveeshb.com/blog/2025/12/azure-functions-sqs-python-support/" rel="alternate" type="text/html" title="Python Support for Azure Functions SQS Extension" /><published>2025-12-29T00:00:00+00:00</published><updated>2025-12-29T00:00:00+00:00</updated><id>https://laveeshb.com/blog/2025/12/azure-functions-sqs-python-support</id><content type="html" xml:base="https://laveeshb.com/blog/2025/12/azure-functions-sqs-python-support/"><![CDATA[<p>The <a href="/2025/12/24/azure-functions-sqs-extension/">Azure Functions SQS Extension</a> now has Python support. If you’re running Azure Functions but need to process messages from Amazon SQS queues, you can now do it in Python with the same patterns you’d use in .NET.</p>

<p><strong>Contents:</strong></p>
<ul>
  <li><a href="#getting-started">Getting Started</a></li>
  <li><a href="#sending-messages">Sending Messages</a></li>
  <li><a href="#authentication">Authentication</a></li>
  <li><a href="#tuning-the-listener">Tuning the Listener</a></li>
  <li><a href="#under-the-hood">Under the Hood</a></li>
  <li><a href="#local-development">Local Development</a></li>
  <li><a href="#source-code">Source Code</a></li>
</ul>

<h2 id="getting-started">Getting Started</h2>

<p>See a complete example in the <a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/python/samples" target="_blank">sample project</a>.</p>

<p>Install the package:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>azure-functions-sqs
</code></pre></div></div>

<p>Write a function that triggers on SQS messages:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">logging</span>

<span class="kn">from</span> <span class="nn">azure_functions_sqs</span> <span class="kn">import</span> <span class="n">SqsTrigger</span><span class="p">,</span> <span class="n">SqsMessage</span>

<span class="n">trigger</span> <span class="o">=</span> <span class="n">SqsTrigger</span><span class="p">(</span><span class="n">queue_url</span><span class="o">=</span><span class="s">"%SQS_QUEUE_URL%"</span><span class="p">)</span>

<span class="o">@</span><span class="n">trigger</span>
<span class="k">def</span> <span class="nf">process_message</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="n">SqsMessage</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="n">logging</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s">"Received: </span><span class="si">{</span><span class="n">message</span><span class="p">.</span><span class="n">message_id</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="n">logging</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s">"Body: </span><span class="si">{</span><span class="n">message</span><span class="p">.</span><span class="n">body</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

    <span class="c1"># Process the message...
</span>    <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">body</span><span class="p">)</span>
    <span class="n">logging</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s">"Order ID: </span><span class="si">{</span><span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'order_id'</span><span class="p">)</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

    <span class="c1"># If this function completes without exception, the message is deleted
</span>    <span class="c1"># If an exception is raised, message becomes visible again for retry
</span></code></pre></div></div>

<p>Configure your queue URL in <a href="https://github.com/laveeshb/azure-functions-sqs-extension/blob/main/python/samples/local.settings.json" target="_blank">local.settings.json</a>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Values"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"AzureWebJobsStorage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UseDevelopmentStorage=true"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FUNCTIONS_WORKER_RUNTIME"</span><span class="p">:</span><span class="w"> </span><span class="s2">"python"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"SQS_QUEUE_URL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://sqs.us-east-1.amazonaws.com/123456789/my-queue"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">SqsMessage</code> class gives you access to all SQS message properties:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">trigger</span>
<span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="n">SqsMessage</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="c1"># Core properties
</span>    <span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">message_id</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">body</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">receipt_handle</span><span class="p">)</span>

    <span class="c1"># System attributes
</span>    <span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">attributes</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"SentTimestamp"</span><span class="p">))</span>
    <span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">attributes</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"ApproximateReceiveCount"</span><span class="p">))</span>

    <span class="c1"># FIFO queue properties
</span>    <span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">attributes</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"MessageGroupId"</span><span class="p">))</span>
    <span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">attributes</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"SequenceNumber"</span><span class="p">))</span>

    <span class="c1"># Custom message attributes
</span>    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">attr</span> <span class="ow">in</span> <span class="n">message</span><span class="p">.</span><span class="n">message_attributes</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">: </span><span class="si">{</span><span class="n">attr</span><span class="p">.</span><span class="n">string_value</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="sending-messages">Sending Messages</h2>

<h3 id="output-binding">Output Binding</h3>

<p>The simplest way to send messages — decorate a function and return the message body (see <a href="https://github.com/laveeshb/azure-functions-sqs-extension/blob/main/python/samples/function_app.py#L73-L100" target="_blank">full example</a>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">azure.functions</span> <span class="k">as</span> <span class="n">func</span>
<span class="kn">from</span> <span class="nn">azure_functions_sqs</span> <span class="kn">import</span> <span class="n">SqsOutput</span>
<span class="kn">import</span> <span class="nn">json</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">func</span><span class="p">.</span><span class="n">FunctionApp</span><span class="p">()</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">SqsOutput</span><span class="p">(</span><span class="n">queue_url</span><span class="o">=</span><span class="s">"%OUTPUT_QUEUE_URL%"</span><span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">function_name</span><span class="p">(</span><span class="s">"SendToSqs"</span><span class="p">)</span>
<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="n">route</span><span class="o">=</span><span class="s">"send"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">"POST"</span><span class="p">])</span>
<span class="o">@</span><span class="n">output</span>
<span class="k">def</span> <span class="nf">send_to_sqs</span><span class="p">(</span><span class="n">req</span><span class="p">:</span> <span class="n">func</span><span class="p">.</span><span class="n">HttpRequest</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="n">body</span> <span class="o">=</span> <span class="n">req</span><span class="p">.</span><span class="n">get_json</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span>
        <span class="s">"timestamp"</span><span class="p">:</span> <span class="n">body</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"timestamp"</span><span class="p">),</span>
        <span class="s">"data"</span><span class="p">:</span> <span class="n">body</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"data"</span><span class="p">),</span>
        <span class="s">"source"</span><span class="p">:</span> <span class="s">"azure-function"</span><span class="p">,</span>
    <span class="p">})</span>
</code></pre></div></div>

<h3 id="batch-sending-with-sqscollector">Batch Sending with SqsCollector</h3>

<p>For sending multiple messages efficiently, use <code class="language-plaintext highlighter-rouge">SqsCollector</code> — it batches messages in groups of 10 (the SQS limit), similar to <code class="language-plaintext highlighter-rouge">IAsyncCollector</code> in .NET (see <a href="https://github.com/laveeshb/azure-functions-sqs-extension/blob/main/python/samples/function_app.py#L107-L148" target="_blank">full example</a>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">azure_functions_sqs</span> <span class="kn">import</span> <span class="n">SqsCollector</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">function_name</span><span class="p">(</span><span class="s">"BatchSendToSqs"</span><span class="p">)</span>
<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="n">route</span><span class="o">=</span><span class="s">"batch"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">"POST"</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">batch_send</span><span class="p">(</span><span class="n">req</span><span class="p">:</span> <span class="n">func</span><span class="p">.</span><span class="n">HttpRequest</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">func</span><span class="p">.</span><span class="n">HttpResponse</span><span class="p">:</span>
    <span class="n">collector</span> <span class="o">=</span> <span class="n">SqsCollector</span><span class="p">(</span><span class="n">queue_url</span><span class="o">=</span><span class="s">"%OUTPUT_QUEUE_URL%"</span><span class="p">)</span>

    <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">req</span><span class="p">.</span><span class="n">get_json</span><span class="p">().</span><span class="n">get</span><span class="p">(</span><span class="s">"items"</span><span class="p">,</span> <span class="p">[]):</span>
        <span class="n">collector</span><span class="p">.</span><span class="n">add</span><span class="p">({</span>
            <span class="s">"item_id"</span><span class="p">:</span> <span class="n">item</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"id"</span><span class="p">),</span>
            <span class="s">"action"</span><span class="p">:</span> <span class="n">item</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"action"</span><span class="p">,</span> <span class="s">"process"</span><span class="p">),</span>
        <span class="p">})</span>

    <span class="n">sent_count</span> <span class="o">=</span> <span class="n">collector</span><span class="p">.</span><span class="n">flush</span><span class="p">()</span>

    <span class="k">return</span> <span class="n">func</span><span class="p">.</span><span class="n">HttpResponse</span><span class="p">(</span>
        <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span><span class="s">"messages_sent"</span><span class="p">:</span> <span class="n">sent_count</span><span class="p">}),</span>
        <span class="n">mimetype</span><span class="o">=</span><span class="s">"application/json"</span><span class="p">,</span>
    <span class="p">)</span>
</code></pre></div></div>

<h3 id="fifo-queues">FIFO Queues</h3>

<p>For FIFO queues (URL ends with <code class="language-plaintext highlighter-rouge">.fifo</code>), specify a message group ID (see <a href="https://github.com/laveeshb/azure-functions-sqs-extension/blob/main/python/samples/function_app.py#L155-L175" target="_blank">full example</a>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">azure_functions_sqs</span> <span class="kn">import</span> <span class="n">SqsOutput</span><span class="p">,</span> <span class="n">SqsOutputOptions</span>

<span class="n">fifo_output</span> <span class="o">=</span> <span class="n">SqsOutput</span><span class="p">(</span>
    <span class="n">queue_url</span><span class="o">=</span><span class="s">"%FIFO_QUEUE_URL%"</span><span class="p">,</span>
    <span class="n">options</span><span class="o">=</span><span class="n">SqsOutputOptions</span><span class="p">(</span><span class="n">message_group_id</span><span class="o">=</span><span class="s">"order-processing"</span><span class="p">),</span>
<span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="n">route</span><span class="o">=</span><span class="s">"fifo"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">"POST"</span><span class="p">])</span>
<span class="o">@</span><span class="n">fifo_output</span>
<span class="k">def</span> <span class="nf">send_to_fifo</span><span class="p">(</span><span class="n">req</span><span class="p">:</span> <span class="n">func</span><span class="p">.</span><span class="n">HttpRequest</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="k">return</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="n">get_json</span><span class="p">())</span>
</code></pre></div></div>

<h2 id="authentication">Authentication</h2>

<p>The extension uses the <a href="https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html" target="_blank">boto3 credential chain</a>. If you don’t provide explicit credentials, the SDK looks for them in this order:</p>

<ol>
  <li>Environment variables (<code class="language-plaintext highlighter-rouge">AWS_ACCESS_KEY_ID</code>, <code class="language-plaintext highlighter-rouge">AWS_SECRET_ACCESS_KEY</code>)</li>
  <li>Shared credentials file (<code class="language-plaintext highlighter-rouge">~/.aws/credentials</code>)</li>
  <li>IAM role for EC2/ECS/EKS</li>
</ol>

<h3 id="local-development">Local Development</h3>

<p>Set environment variables in <code class="language-plaintext highlighter-rouge">local.settings.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Values"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"AWS_ACCESS_KEY_ID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"your-access-key"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AWS_SECRET_ACCESS_KEY"</span><span class="p">:</span><span class="w"> </span><span class="s2">"your-secret-key"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AWS_REGION"</span><span class="p">:</span><span class="w"> </span><span class="s2">"us-east-1"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Or configure a profile with the AWS CLI:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws configure
</code></pre></div></div>

<h3 id="workload-identity-federation-recommended-for-production">Workload Identity Federation (Recommended for Production)</h3>

<p>The best approach for Azure-hosted functions is <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html" target="_blank">OIDC federation</a> between Azure AD and AWS. No stored credentials — your function uses its Azure Managed Identity to assume an AWS IAM role.</p>

<ol>
  <li>Create an OIDC Identity Provider in AWS IAM that trusts Azure AD</li>
  <li>Create an IAM role with a trust policy for your Azure Function’s managed identity</li>
  <li>Use <code class="language-plaintext highlighter-rouge">AssumeRoleWithWebIdentity</code> to get temporary AWS credentials</li>
</ol>

<p>This eliminates credential management entirely. See <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html" target="_blank">AWS docs</a> for setup details.</p>

<h3 id="azure-key-vault-alternative">Azure Key Vault (Alternative)</h3>

<p>If federation isn’t an option, store your AWS credentials in <a href="https://learn.microsoft.com/azure/app-service/app-service-key-vault-references" target="_blank">Azure Key Vault</a> and reference them in app settings:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AWS_ACCESS_KEY_ID=@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/aws-key-id/)
AWS_SECRET_ACCESS_KEY=@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/aws-secret-key/)
</code></pre></div></div>

<h2 id="tuning-the-listener">Tuning the Listener</h2>

<p>Configure polling behavior with <code class="language-plaintext highlighter-rouge">SqsTriggerOptions</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">timedelta</span>
<span class="kn">from</span> <span class="nn">azure_functions_sqs</span> <span class="kn">import</span> <span class="n">SqsTrigger</span><span class="p">,</span> <span class="n">SqsTriggerOptions</span>

<span class="n">trigger</span> <span class="o">=</span> <span class="n">SqsTrigger</span><span class="p">(</span>
    <span class="n">queue_url</span><span class="o">=</span><span class="s">"%SQS_QUEUE_URL%"</span><span class="p">,</span>
    <span class="n">options</span><span class="o">=</span><span class="n">SqsTriggerOptions</span><span class="p">(</span>
        <span class="n">max_number_of_messages</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span>
        <span class="n">visibility_timeout</span><span class="o">=</span><span class="n">timedelta</span><span class="p">(</span><span class="n">seconds</span><span class="o">=</span><span class="mi">30</span><span class="p">),</span>
        <span class="n">polling_interval</span><span class="o">=</span><span class="n">timedelta</span><span class="p">(</span><span class="n">seconds</span><span class="o">=</span><span class="mi">5</span><span class="p">),</span>
    <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Setting</th>
      <th>Default</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">max_number_of_messages</code></td>
      <td>10</td>
      <td>Messages to receive per poll (1-10)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">visibility_timeout</code></td>
      <td>30 seconds</td>
      <td>How long a message stays hidden while processing</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">polling_interval</code></td>
      <td>5 seconds</td>
      <td>Delay between polls when queue is empty</td>
    </tr>
  </tbody>
</table>

<h2 id="under-the-hood">Under the Hood</h2>

<p>The trigger uses a sequential polling loop:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────────────┐
│                         Polling Loop                            │
└─────────────────────────────────────────────────────────────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │  ReceiveMessage     │
                    │  (long poll, 20s)   │
                    └─────────────────────┘
                               │
              ┌────────────────┴────────────────┐
              │                                 │
              ▼                                 ▼
     ┌─────────────────┐              ┌─────────────────┐
     │  Messages?  Yes │              │  Messages?  No  │
     └─────────────────┘              └─────────────────┘
              │                                 │
              ▼                                 ▼
     ┌─────────────────┐              ┌─────────────────┐
     │ Execute Function│              │ Wait interval   │
     └─────────────────┘              │ (5s default)    │
              │                       └─────────────────┘
              │                                 │
      ┌───────┴───────┐                        │
      │               │                        │
      ▼               ▼                        │
┌──────────┐   ┌──────────┐                    │
│ Success  │   │ Failure  │                    │
└──────────┘   └──────────┘                    │
      │               │                        │
      ▼               ▼                        │
┌──────────┐   ┌──────────────┐                │
│ Delete   │   │ Leave in     │                │
│ message  │   │ queue (retry)│                │
└──────────┘   └──────────────┘                │
      │               │                        │
      └───────┬───────┘                        │
              │                                │
              └────────────────┬───────────────┘
                               │
                               ▼
                         (repeat loop)
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">_poll_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">while</span> <span class="bp">self</span><span class="p">.</span><span class="n">_running</span><span class="p">:</span>
        <span class="n">messages_received</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="p">.</span><span class="n">_poll_and_process</span><span class="p">()</span>

        <span class="k">if</span> <span class="n">messages_received</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
            <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">_options</span><span class="p">.</span><span class="n">polling_interval</span><span class="p">.</span><span class="n">total_seconds</span><span class="p">())</span>
</code></pre></div></div>

<p>Long polling (20-second wait time) reduces API calls — instead of hitting SQS every second, the SDK waits for messages to arrive or the timeout to expire.</p>

<h2 id="local-development-1">Local Development</h2>

<p>The extension works with <a href="https://localstack.cloud/" target="_blank">LocalStack</a> for local development without AWS credentials:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">trigger</span> <span class="o">=</span> <span class="n">SqsTrigger</span><span class="p">(</span>
    <span class="n">queue_url</span><span class="o">=</span><span class="s">"http://localhost:4566/000000000000/test-queue"</span><span class="p">,</span>
    <span class="n">region</span><span class="o">=</span><span class="s">"us-east-1"</span><span class="p">,</span>
    <span class="n">aws_key_id</span><span class="o">=</span><span class="s">"test"</span><span class="p">,</span>
    <span class="n">aws_access_key</span><span class="o">=</span><span class="s">"test"</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>

<p>See the <a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/dotnet/localstack" target="_blank">LocalStack guide</a> for Docker setup. The same scripts work for Python.</p>

<h2 id="source-code">Source Code</h2>

<p>The extension is <a href="https://github.com/laveeshb/azure-functions-sqs-extension" target="_blank">open source on GitHub</a>. Key files:</p>

<ul>
  <li><a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/python/azure_functions_sqs" target="_blank">Python extension source</a></li>
  <li><a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/python/samples" target="_blank">Sample functions</a></li>
  <li><a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/python/tests" target="_blank">Tests</a></li>
</ul>]]></content><author><name>Laveesh Bansal</name></author><category term="azure" /><category term="aws" /><category term="python" /><category term="azure-functions" /><category term="sqs" /><category term="python" /><category term="serverless" /><summary type="html"><![CDATA[The Azure Functions SQS extension now supports Python — trigger functions from SQS queues and send messages back using familiar decorator patterns.]]></summary></entry><entry><title type="html">Building Azure Functions in Go</title><link href="https://laveeshb.com/blog/2025/12/azure-functions-go-worker/" rel="alternate" type="text/html" title="Building Azure Functions in Go" /><published>2025-12-25T00:00:00+00:00</published><updated>2025-12-25T00:00:00+00:00</updated><id>https://laveeshb.com/blog/2025/12/azure-functions-go-worker</id><content type="html" xml:base="https://laveeshb.com/blog/2025/12/azure-functions-go-worker/"><![CDATA[<p>I’ve been exploring how to run <a href="https://github.com/laveeshb/azure-functions-go-worker" target="_blank">Azure Functions in Go</a> using the <a href="https://learn.microsoft.com/azure/azure-functions/functions-custom-handlers" target="_blank">Custom Handler</a> pattern. No special SDK, no code generation — just standard Go with <code class="language-plaintext highlighter-rouge">net/http</code>.</p>

<p>Here’s how it works.</p>

<p><strong>Contents:</strong></p>
<ul>
  <li><a href="#why-this-approach">Why This Approach</a></li>
  <li><a href="#the-architecture">The Architecture</a></li>
  <li><a href="#writing-a-function">Writing a Function</a></li>
  <li><a href="#the-qr-generator-sample">The QR Generator Sample</a></li>
  <li><a href="#deploying-to-azure">Deploying to Azure</a></li>
  <li><a href="#try-it">Try It</a></li>
</ul>

<h2 id="why-this-approach">Why This Approach</h2>

<p>Azure Functions has built-in support for languages like C#, JavaScript, Python, and Java. For Go, the recommended approach is <strong>Custom Handlers</strong> — the host starts your HTTP server and forwards requests.</p>

<p>Why this works well:</p>

<ul>
  <li><strong>No SDK required</strong> — Use standard <code class="language-plaintext highlighter-rouge">net/http</code>, the same code runs anywhere</li>
  <li><strong>Simple architecture</strong> — Just an HTTP server, nothing special</li>
  <li><strong>Easy debugging</strong> — Test locally with <code class="language-plaintext highlighter-rouge">go run</code> or any HTTP client</li>
  <li><strong>Production ready</strong> — Custom Handlers are a documented, stable feature</li>
  <li><strong>Portable</strong> — Your code isn’t tied to Azure, it’s just a Go web server</li>
</ul>

<h2 id="the-architecture">The Architecture</h2>

<p>The Azure Functions Host starts your Go binary and forwards HTTP requests:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Internet → Azure Functions Host → Your Go Binary (net/http server)
</code></pre></div></div>

<p>Your binary reads the port from an environment variable and serves requests (<a href="https://github.com/laveeshb/azure-functions-go-worker/blob/main/samples/hello-world/src/main.go" target="_blank">main.go</a>):</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">port</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"FUNCTIONS_CUSTOMHANDLER_PORT"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">port</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
        <span class="n">port</span> <span class="o">=</span> <span class="s">"8080"</span>
    <span class="p">}</span>

    <span class="n">http</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/api/hello"</span><span class="p">,</span> <span class="n">helloHandler</span><span class="p">)</span>
    <span class="n">http</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/api/health"</span><span class="p">,</span> <span class="n">healthHandler</span><span class="p">)</span>

    <span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Starting server on port %s"</span><span class="p">,</span> <span class="n">port</span><span class="p">)</span>
    <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">ListenAndServe</span><span class="p">(</span><span class="s">":"</span><span class="o">+</span><span class="n">port</span><span class="p">,</span> <span class="no">nil</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <a href="https://github.com/laveeshb/azure-functions-go-worker/blob/main/samples/hello-world/src/host.json" target="_blank">host.json</a> tells Azure Functions to use your binary as a Custom Handler:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"customHandler"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"defaultExecutablePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"handler"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"enableForwardingHttpRequest"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>With <code class="language-plaintext highlighter-rouge">enableForwardingHttpRequest: true</code>, the host passes through HTTP requests directly — your Go code sees standard <code class="language-plaintext highlighter-rouge">http.Request</code> and <code class="language-plaintext highlighter-rouge">http.ResponseWriter</code>.</p>

<h2 id="writing-a-function">Writing a Function</h2>

<p>A simple hello function:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">helloHandler</span><span class="p">(</span><span class="n">w</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">r</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">name</span> <span class="o">:=</span> <span class="n">r</span><span class="o">.</span><span class="n">URL</span><span class="o">.</span><span class="n">Query</span><span class="p">()</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"name"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">name</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
        <span class="n">name</span> <span class="o">=</span> <span class="s">"World"</span>
    <span class="p">}</span>

    <span class="n">response</span> <span class="o">:=</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span>
        <span class="s">"message"</span><span class="o">:</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"Hello, %s!"</span><span class="p">,</span> <span class="n">name</span><span class="p">),</span>
    <span class="p">}</span>

    <span class="n">w</span><span class="o">.</span><span class="n">Header</span><span class="p">()</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">)</span>
    <span class="n">json</span><span class="o">.</span><span class="n">NewEncoder</span><span class="p">(</span><span class="n">w</span><span class="p">)</span><span class="o">.</span><span class="n">Encode</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Each function needs a <a href="https://github.com/laveeshb/azure-functions-go-worker/blob/main/samples/hello-world/src/Hello/function.json" target="_blank">function.json</a> that defines its trigger:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"bindings"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"authLevel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"anonymous"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"httpTrigger"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="s2">"in"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"req"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"methods"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"get"</span><span class="p">,</span><span class="w"> </span><span class="s2">"post"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"route"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hello"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="s2">"out"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$return"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>That’s it. Standard Go HTTP handlers, standard JSON responses.</p>

<h2 id="the-qr-generator-sample">The QR Generator Sample</h2>

<p>The repo includes a <a href="https://github.com/laveeshb/azure-functions-go-worker/tree/main/samples/qr-generator" target="_blank">complete QR code generator</a> with a web UI (<a href="https://github.com/laveeshb/azure-functions-go-worker/blob/main/samples/qr-generator/main.go" target="_blank">main.go</a>):</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">handleGenerate</span><span class="p">(</span><span class="n">w</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">r</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">Method</span> <span class="o">==</span> <span class="n">http</span><span class="o">.</span><span class="n">MethodGet</span> <span class="p">{</span>
        <span class="c">// Serve the web UI</span>
        <span class="n">w</span><span class="o">.</span><span class="n">Header</span><span class="p">()</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"text/html"</span><span class="p">)</span>
        <span class="n">fmt</span><span class="o">.</span><span class="n">Fprint</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="n">landingPageHTML</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="p">}</span>

    <span class="c">// POST: Generate QR code</span>
    <span class="k">var</span> <span class="n">req</span> <span class="n">GenerateRequest</span>
    <span class="n">json</span><span class="o">.</span><span class="n">NewDecoder</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span><span class="o">.</span><span class="n">Decode</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">)</span>

    <span class="n">png</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">qrcode</span><span class="o">.</span><span class="n">Encode</span><span class="p">(</span><span class="n">req</span><span class="o">.</span><span class="n">Content</span><span class="p">,</span> <span class="n">qrcode</span><span class="o">.</span><span class="n">Medium</span><span class="p">,</span> <span class="n">req</span><span class="o">.</span><span class="n">Size</span><span class="p">)</span>

    <span class="n">json</span><span class="o">.</span><span class="n">NewEncoder</span><span class="p">(</span><span class="n">w</span><span class="p">)</span><span class="o">.</span><span class="n">Encode</span><span class="p">(</span><span class="n">GenerateResponse</span><span class="p">{</span>
        <span class="n">Image</span><span class="o">:</span>   <span class="n">base64</span><span class="o">.</span><span class="n">StdEncoding</span><span class="o">.</span><span class="n">EncodeToString</span><span class="p">(</span><span class="n">png</span><span class="p">),</span>
        <span class="n">Content</span><span class="o">:</span> <span class="n">req</span><span class="o">.</span><span class="n">Content</span><span class="p">,</span>
        <span class="n">Size</span><span class="o">:</span>    <span class="n">req</span><span class="o">.</span><span class="n">Size</span><span class="p">,</span>
    <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The web UI lets users enter text and see the generated QR code:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">GET /generate</code> — Interactive web page</li>
  <li><code class="language-plaintext highlighter-rouge">POST /generate</code> — API returns base64-encoded PNG</li>
  <li><code class="language-plaintext highlighter-rouge">GET /health</code> — Health check endpoint</li>
</ul>

<h2 id="deploying-to-azure">Deploying to Azure</h2>

<p>Each sample includes deployment scripts (<a href="https://github.com/laveeshb/azure-functions-go-worker/blob/main/samples/qr-generator/deploy.sh" target="_blank">deploy.sh</a>, <a href="https://github.com/laveeshb/azure-functions-go-worker/blob/main/samples/qr-generator/deploy.ps1" target="_blank">deploy.ps1</a>). For the QR generator:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>samples/qr-generator

<span class="c"># Deploy to Azure (creates everything: resource group, storage, function app)</span>
./deploy.sh <span class="nt">-g</span> my-resource-group <span class="nt">-l</span> westus2

<span class="c"># Or on Windows</span>
.<span class="se">\d</span>eploy.ps1 <span class="nt">-ResourceGroupName</span> my-resource-group <span class="nt">-Location</span> westus2
</code></pre></div></div>

<p>The script:</p>
<ol>
  <li>Cross-compiles to Linux (<code class="language-plaintext highlighter-rouge">GOOS=linux GOARCH=amd64</code>)</li>
  <li>Creates Azure resources (resource group, storage account, function app)</li>
  <li>Deploys with <code class="language-plaintext highlighter-rouge">func azure functionapp publish</code></li>
</ol>

<p>For local development:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Build</span>
go build <span class="nt">-o</span> handler <span class="nb">.</span>

<span class="c"># Run with Azure Functions Core Tools</span>
func start
</code></pre></div></div>

<h2 id="try-it">Try It</h2>

<p>Clone the repo and try the samples:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/laveeshb/azure-functions-go-worker.git
<span class="nb">cd </span>azure-functions-go-worker

<span class="c"># Hello World - basic HTTP function</span>
<span class="nb">cd </span>samples/hello-world/src
go build <span class="nt">-o</span> handler <span class="nb">.</span>
func start

<span class="c"># QR Generator - web UI + API</span>
<span class="nb">cd </span>samples/qr-generator
go build <span class="nt">-o</span> handler <span class="nb">.</span>
func start
</code></pre></div></div>

<p>The code is on <a href="https://github.com/laveeshb/azure-functions-go-worker" target="_blank">GitHub</a>.</p>]]></content><author><name>Laveesh Bansal</name></author><category term="azure" /><category term="azure-functions" /><category term="go" /><category term="serverless" /><summary type="html"><![CDATA[Write Azure Functions in Go — no SDK required, just standard net/http.]]></summary></entry><entry><title type="html">Azure Functions Extension for Amazon SQS</title><link href="https://laveeshb.com/blog/2025/12/azure-functions-sqs-extension/" rel="alternate" type="text/html" title="Azure Functions Extension for Amazon SQS" /><published>2025-12-24T00:00:00+00:00</published><updated>2025-12-24T00:00:00+00:00</updated><id>https://laveeshb.com/blog/2025/12/azure-functions-sqs-extension</id><content type="html" xml:base="https://laveeshb.com/blog/2025/12/azure-functions-sqs-extension/"><![CDATA[<p>If you’re on <a href="https://learn.microsoft.com/azure/azure-functions/functions-overview" target="_blank">Azure Functions</a> but have data sitting in <a href="https://aws.amazon.com/sqs/" target="_blank">Amazon SQS</a> queues, this <a href="https://github.com/laveeshb/azure-functions-sqs-extension" target="_blank">extension</a> bridges that gap. It provides native bindings for SQS — trigger functions when messages arrive, send messages out, all with familiar Azure Functions patterns.</p>

<p><strong>Contents:</strong></p>
<ul>
  <li><a href="#why-this-exists">Why This Exists</a></li>
  <li><a href="#how-it-works">How It Works</a></li>
  <li><a href="#two-flavors">Two Flavors</a></li>
  <li><a href="#getting-started">Getting Started</a></li>
  <li><a href="#authentication">Authentication</a></li>
  <li><a href="#tuning-the-listener">Tuning the Listener</a></li>
  <li><a href="#under-the-hood">Under the Hood</a></li>
  <li><a href="#local-development">Local Development</a></li>
  <li><a href="#source-code">Source Code</a></li>
</ul>

<h2 id="why-this-exists">Why This Exists</h2>

<p>Real-world systems span services and clouds — data doesn’t always live where your compute does. You might have:</p>

<ul>
  <li>Legacy systems that push to SQS</li>
  <li>Third-party integrations that only support AWS</li>
  <li>Partner data landing in SQS queues</li>
  <li>Teams using different cloud providers</li>
</ul>

<p>Whatever the reason, if your compute is on Azure Functions and your messages are in SQS, you need a way to connect them.</p>

<h2 id="how-it-works">How It Works</h2>

<p>The extension polls SQS using long polling (20-second wait), receives messages, and triggers your function. If your function succeeds, the message is deleted. If it fails, the message becomes visible again after the visibility timeout — built-in retry for free.</p>

<h2 id="two-flavors">Two Flavors</h2>

<p>Azure Functions has two hosting models, and this extension supports both:</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>Package</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>In-Process</td>
      <td><a href="https://www.nuget.org/packages/Extensions.Azure.WebJobs.SQS" target="_blank">Extensions.Azure.WebJobs.SQS</a></td>
      <td>Supported (retiring Nov 2026)</td>
    </tr>
    <tr>
      <td>Isolated Worker</td>
      <td><a href="https://www.nuget.org/packages/Extensions.Azure.Functions.Worker.SQS" target="_blank">Extensions.Azure.Functions.Worker.SQS</a></td>
      <td>Recommended</td>
    </tr>
  </tbody>
</table>

<p>New projects should use the isolated worker model. It runs in a separate process, supports any .NET version, and is the future of Azure Functions.</p>

<h2 id="getting-started">Getting Started</h2>

<h3 id="isolated-worker-recommended">Isolated Worker (Recommended)</h3>

<p>See a complete example in the <a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/dotnet/test/Extensions.SQS.Test.Isolated" target="_blank">sample project</a>.</p>

<p>Add the NuGet package:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package Extensions.Azure.Functions.Worker.SQS
</code></pre></div></div>

<p>Register the extension in <code class="language-plaintext highlighter-rouge">Program.cs</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">host</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HostBuilder</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">ConfigureFunctionsWorkerDefaults</span><span class="p">(</span><span class="n">worker</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="n">worker</span><span class="p">.</span><span class="nf">UseSqs</span><span class="p">();</span>
    <span class="p">})</span>
    <span class="p">.</span><span class="nf">Build</span><span class="p">();</span>

<span class="n">host</span><span class="p">.</span><span class="nf">Run</span><span class="p">();</span>
</code></pre></div></div>

<p>Write a function:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">SqsFunction</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">SqsFunction</span><span class="p">&gt;</span> <span class="n">_logger</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">SqsFunction</span><span class="p">(</span><span class="n">ILogger</span><span class="p">&lt;</span><span class="n">SqsFunction</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="p">[</span><span class="nf">Function</span><span class="p">(</span><span class="s">"ProcessMessage"</span><span class="p">)]</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Run</span><span class="p">([</span><span class="nf">SqsTrigger</span><span class="p">(</span><span class="s">"%SQS_QUEUE_URL%"</span><span class="p">)]</span> <span class="n">Message</span> <span class="n">message</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_logger</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">"Received: {MessageId}"</span><span class="p">,</span> <span class="n">message</span><span class="p">.</span><span class="n">MessageId</span><span class="p">);</span>
        <span class="n">_logger</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">"Body: {Body}"</span><span class="p">,</span> <span class="n">message</span><span class="p">.</span><span class="n">Body</span><span class="p">);</span>

        <span class="c1">// Process the message...</span>
        <span class="c1">// If this method completes without exception, the message is deleted</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="in-process-model">In-Process Model</h3>

<p>See a complete example in the <a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/dotnet/test/Extensions.SQS.Test.InProcess" target="_blank">sample project</a>.</p>

<p>Add the NuGet package:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package Extensions.Azure.WebJobs.SQS
</code></pre></div></div>

<p>The extension auto-registers via WebJobs startup. Just write your function:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">SqsFunction</span>
<span class="p">{</span>
    <span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"ProcessMessage"</span><span class="p">)]</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Run</span><span class="p">(</span>
        <span class="p">[</span><span class="nf">SqsQueueTrigger</span><span class="p">(</span><span class="n">QueueUrl</span> <span class="p">=</span> <span class="s">"%SQS_QUEUE_URL%"</span><span class="p">)]</span> <span class="n">Message</span> <span class="n">message</span><span class="p">,</span>
        <span class="n">ILogger</span> <span class="n">log</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">log</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">"Received: {Body}"</span><span class="p">,</span> <span class="n">message</span><span class="p">.</span><span class="n">Body</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="sending-messages">Sending Messages</h3>

<p>For the in-process model, there’s an output binding:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"SendMessage"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">Run</span><span class="p">(</span>
    <span class="p">[</span><span class="nf">HttpTrigger</span><span class="p">(</span><span class="n">AuthorizationLevel</span><span class="p">.</span><span class="n">Function</span><span class="p">,</span> <span class="s">"post"</span><span class="p">)]</span> <span class="n">HttpRequest</span> <span class="n">req</span><span class="p">,</span>
    <span class="p">[</span><span class="nf">SqsQueueOut</span><span class="p">(</span><span class="n">QueueUrl</span> <span class="p">=</span> <span class="s">"%SQS_QUEUE_URL%"</span><span class="p">)]</span> <span class="n">IAsyncCollector</span><span class="p">&lt;</span><span class="n">SqsQueueMessage</span><span class="p">&gt;</span> <span class="n">messages</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">await</span> <span class="n">messages</span><span class="p">.</span><span class="nf">AddAsync</span><span class="p">(</span><span class="k">new</span> <span class="n">SqsQueueMessage</span>
    <span class="p">{</span>
        <span class="n">Body</span> <span class="p">=</span> <span class="s">"Hello from Azure Functions!"</span><span class="p">,</span>
        <span class="n">MessageAttributes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">MessageAttributeValue</span><span class="p">&gt;</span>
        <span class="p">{</span>
            <span class="p">[</span><span class="s">"Source"</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">MessageAttributeValue</span>
            <span class="p">{</span>
                <span class="n">DataType</span> <span class="p">=</span> <span class="s">"String"</span><span class="p">,</span>
                <span class="n">StringValue</span> <span class="p">=</span> <span class="s">"AzureFunctions"</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">});</span>

    <span class="k">return</span> <span class="k">new</span> <span class="nf">OkObjectResult</span><span class="p">(</span><span class="s">"Message sent"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For isolated worker, use the output binding on the return type:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Function</span><span class="p">(</span><span class="s">"SendMessage"</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">SqsOutput</span><span class="p">(</span><span class="s">"%SQS_QUEUE_URL%"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">Run</span><span class="p">([</span><span class="nf">HttpTrigger</span><span class="p">(</span><span class="n">AuthorizationLevel</span><span class="p">.</span><span class="n">Function</span><span class="p">,</span> <span class="s">"post"</span><span class="p">)]</span> <span class="n">HttpRequest</span> <span class="n">req</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="s">"Hello from Azure Functions!"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For more control (batch sends, message attributes, delays), use the AWS SDK directly:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Function</span><span class="p">(</span><span class="s">"SendMessageWithAttributes"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">Run</span><span class="p">(</span>
    <span class="p">[</span><span class="nf">HttpTrigger</span><span class="p">(</span><span class="n">AuthorizationLevel</span><span class="p">.</span><span class="n">Function</span><span class="p">,</span> <span class="s">"post"</span><span class="p">)]</span> <span class="n">HttpRequest</span> <span class="n">req</span><span class="p">,</span>
    <span class="p">[</span><span class="n">FromServices</span><span class="p">]</span> <span class="n">IAmazonSQS</span> <span class="n">sqsClient</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">await</span> <span class="n">sqsClient</span><span class="p">.</span><span class="nf">SendMessageAsync</span><span class="p">(</span><span class="k">new</span> <span class="n">SendMessageRequest</span>
    <span class="p">{</span>
        <span class="n">QueueUrl</span> <span class="p">=</span> <span class="n">Environment</span><span class="p">.</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s">"SQS_QUEUE_URL"</span><span class="p">),</span>
        <span class="n">MessageBody</span> <span class="p">=</span> <span class="s">"Hello!"</span><span class="p">,</span>
        <span class="n">DelaySeconds</span> <span class="p">=</span> <span class="m">5</span><span class="p">,</span>
        <span class="n">MessageAttributes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">MessageAttributeValue</span><span class="p">&gt;</span>
        <span class="p">{</span>
            <span class="p">[</span><span class="s">"Source"</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">MessageAttributeValue</span>
            <span class="p">{</span>
                <span class="n">DataType</span> <span class="p">=</span> <span class="s">"String"</span><span class="p">,</span>
                <span class="n">StringValue</span> <span class="p">=</span> <span class="s">"AzureFunctions"</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">});</span>

    <span class="k">return</span> <span class="k">new</span> <span class="nf">OkResult</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="configuration">Configuration</h3>

<p>Set your queue URL in <code class="language-plaintext highlighter-rouge">local.settings.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Values"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"AzureWebJobsStorage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UseDevelopmentStorage=true"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FUNCTIONS_WORKER_RUNTIME"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dotnet-isolated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"SQS_QUEUE_URL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://sqs.us-east-1.amazonaws.com/123456789/my-queue"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="authentication">Authentication</h2>

<p>The extension uses the <a href="https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/creds-assign.html" target="_blank">AWS SDK credential chain</a>. If you don’t provide explicit credentials, the SDK looks for them in this order:</p>

<ol>
  <li>Environment variables (<code class="language-plaintext highlighter-rouge">AWS_ACCESS_KEY_ID</code>, <code class="language-plaintext highlighter-rouge">AWS_SECRET_ACCESS_KEY</code>)</li>
  <li>Shared credentials file (<code class="language-plaintext highlighter-rouge">~/.aws/credentials</code>)</li>
  <li>IAM role for EC2/ECS/EKS</li>
</ol>

<h3 id="local-development">Local Development</h3>

<p>For local development, set environment variables in <code class="language-plaintext highlighter-rouge">local.settings.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Values"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"AWS_ACCESS_KEY_ID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"your-access-key"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AWS_SECRET_ACCESS_KEY"</span><span class="p">:</span><span class="w"> </span><span class="s2">"your-secret-key"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AWS_REGION"</span><span class="p">:</span><span class="w"> </span><span class="s2">"us-east-1"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Or use the AWS CLI to configure a profile:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws configure
</code></pre></div></div>

<p>The SDK will pick up credentials from <code class="language-plaintext highlighter-rouge">~/.aws/credentials</code> automatically.</p>

<h3 id="workload-identity-federation-recommended-for-production">Workload Identity Federation (Recommended for Production)</h3>

<p>The best approach for Azure-hosted functions is <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html" target="_blank">OIDC federation</a> between Azure AD and AWS. No stored credentials — your function uses its Azure Managed Identity to assume an AWS IAM role.</p>

<ol>
  <li>Create an OIDC Identity Provider in AWS IAM that trusts Azure AD</li>
  <li>Create an IAM role with a trust policy for your Azure Function’s managed identity</li>
  <li>Use <code class="language-plaintext highlighter-rouge">AssumeRoleWithWebIdentity</code> to get temporary AWS credentials</li>
</ol>

<p>This eliminates credential management entirely. See <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html" target="_blank">AWS docs</a> for setup details.</p>

<h3 id="azure-key-vault-alternative">Azure Key Vault (Alternative)</h3>

<p>If federation isn’t an option, store your AWS credentials in <a href="https://learn.microsoft.com/azure/app-service/app-service-key-vault-references" target="_blank">Azure Key Vault</a> and reference them in app settings:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AWS_ACCESS_KEY_ID=@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/aws-key-id/)
AWS_SECRET_ACCESS_KEY=@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/aws-secret-key/)
</code></pre></div></div>

<p>This keeps secrets out of your code, though you’ll need to manage rotation.</p>

<h3 id="iam-roles-self-hosted-on-aws">IAM Roles (Self-Hosted on AWS)</h3>

<p>If you’re self-hosting Azure Functions on AWS infrastructure (EC2, ECS, EKS), you can use IAM roles instead of managing credentials.</p>

<p><strong>EC2</strong>: Attach an IAM role with <code class="language-plaintext highlighter-rouge">sqs:ReceiveMessage</code>, <code class="language-plaintext highlighter-rouge">sqs:DeleteMessage</code>, and <code class="language-plaintext highlighter-rouge">sqs:GetQueueAttributes</code> permissions.</p>

<p><strong>ECS/Fargate</strong>: Use a <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html" target="_blank">task IAM role</a>.</p>

<p><strong>EKS/Kubernetes</strong>: Use <a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html" target="_blank">IAM Roles for Service Accounts (IRSA)</a>.</p>

<p>Example IAM policy:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"sqs:ReceiveMessage"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"sqs:DeleteMessage"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"sqs:GetQueueAttributes"</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:sqs:us-east-1:123456789012:my-queue"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="tuning-the-listener">Tuning the Listener</h2>

<p>Configure polling behavior in <code class="language-plaintext highlighter-rouge">host.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"extensions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"sqsQueue"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"maxNumberOfMessages"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w">
      </span><span class="nl">"pollingInterval"</span><span class="p">:</span><span class="w"> </span><span class="s2">"00:00:05"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"visibilityTimeout"</span><span class="p">:</span><span class="w"> </span><span class="s2">"00:00:30"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Setting</th>
      <th>Default</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">maxNumberOfMessages</code></td>
      <td><code class="language-plaintext highlighter-rouge">1</code></td>
      <td>Number of messages to retrieve per poll (1-10)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pollingInterval</code></td>
      <td><code class="language-plaintext highlighter-rouge">00:00:05</code></td>
      <td>Delay between polls when the queue is empty</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">visibilityTimeout</code></td>
      <td><code class="language-plaintext highlighter-rouge">00:00:30</code></td>
      <td>How long a message stays hidden while being processed</td>
    </tr>
  </tbody>
</table>

<h2 id="under-the-hood">Under the Hood</h2>

<p>The listener uses a sequential polling loop rather than a timer:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────────────┐
│                         Polling Loop                            │
└─────────────────────────────────────────────────────────────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │  ReceiveMessage     │
                    │  (long poll, 20s)   │
                    └─────────────────────┘
                               │
              ┌────────────────┴────────────────┐
              │                                 │
              ▼                                 ▼
     ┌─────────────────┐              ┌─────────────────┐
     │  Messages?  Yes │              │  Messages?  No  │
     └─────────────────┘              └─────────────────┘
              │                                 │
              ▼                                 ▼
     ┌─────────────────┐              ┌─────────────────┐
     │ Execute Function│              │ Wait interval   │
     └─────────────────┘              │ (5s default)    │
              │                       └─────────────────┘
              │                                 │
      ┌───────┴───────┐                        │
      │               │                        │
      ▼               ▼                        │
┌──────────┐   ┌──────────┐                    │
│ Success  │   │ Failure  │                    │
└──────────┘   └──────────┘                    │
      │               │                        │
      ▼               ▼                        │
┌──────────┐   ┌──────────────┐                │
│ Delete   │   │ Leave in     │                │
│ message  │   │ queue (retry)│                │
└──────────┘   └──────────────┘                │
      │               │                        │
      └───────┬───────┘                        │
              │                                │
              └────────────────┬───────────────┘
                               │
                               ▼
                         (repeat loop)
</code></pre></div></div>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="p">(</span><span class="n">_isRunning</span> <span class="p">&amp;&amp;</span> <span class="p">!</span><span class="n">cancellationToken</span><span class="p">.</span><span class="n">IsCancellationRequested</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">messagesReceived</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">PollAndProcessAsync</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">messagesReceived</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="n">_options</span><span class="p">.</span><span class="n">PollingInterval</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This avoids overlapping polls that can happen with timer-based approaches. The full implementation is in <a href="https://github.com/laveeshb/azure-functions-sqs-extension/blob/main/dotnet/src/Azure.WebJobs.Extensions.SQS/Trigger/SqsQueueTriggerListener.cs" target="_blank">SqsQueueTriggerListener.cs</a>.</p>

<p>Long polling (20-second wait time) reduces API calls significantly — instead of hitting SQS every second, the SDK waits for messages to arrive or the timeout to expire.</p>

<h2 id="local-development-1">Local Development</h2>

<p>The extension works with <a href="https://localstack.cloud/" target="_blank">LocalStack</a> for local development without real AWS credentials. Setup scripts are in the repo:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Unix/Mac</span>
./dotnet/localstack/unix/setup-localstack.sh

<span class="c"># Windows (PowerShell)</span>
.<span class="se">\d</span>otnet<span class="se">\l</span>ocalstack<span class="se">\w</span>indows<span class="se">\s</span>etup-localstack.ps1
</code></pre></div></div>

<p>This starts LocalStack and creates test queues. Configure your <code class="language-plaintext highlighter-rouge">local.settings.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Values"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"AWS_ACCESS_KEY_ID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"test"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AWS_SECRET_ACCESS_KEY"</span><span class="p">:</span><span class="w"> </span><span class="s2">"test"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AWS_REGION"</span><span class="p">:</span><span class="w"> </span><span class="s2">"us-east-1"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AWS_ENDPOINT_URL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:4566"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AWS:ServiceURL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:4566"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"SQS_QUEUE_URL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:4566/000000000000/test-queue"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">AWS:ServiceURL</code> setting is needed for the in-process model to connect to LocalStack. See the <a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/dotnet/localstack" target="_blank">LocalStack guide</a> in the repo for detailed setup instructions.</p>

<h2 id="source-code">Source Code</h2>

<p>The extension is <a href="https://github.com/laveeshb/azure-functions-sqs-extension" target="_blank">open source on GitHub</a>. Key files:</p>

<ul>
  <li><a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/dotnet/src/Azure.WebJobs.Extensions.SQS" target="_blank">In-process extension</a></li>
  <li><a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/dotnet/src/Azure.Functions.Worker.Extensions.SQS" target="_blank">Isolated worker extension</a></li>
  <li><a href="https://github.com/laveeshb/azure-functions-sqs-extension/tree/main/dotnet/test" target="_blank">Sample functions</a></li>
</ul>

<p>Contributions welcome.</p>]]></content><author><name>Laveesh Bansal</name></author><category term="azure" /><category term="aws" /><category term="azure-functions" /><category term="sqs" /><category term="dotnet" /><category term="serverless" /><summary type="html"><![CDATA[An Azure Functions extension that lets you trigger functions from SQS queues and send messages back.]]></summary></entry><entry><title type="html">Building an MCP Server for Azure Logic Apps</title><link href="https://laveeshb.com/blog/2025/12/building-mcp-server-for-azure-logic-apps/" rel="alternate" type="text/html" title="Building an MCP Server for Azure Logic Apps" /><published>2025-12-24T00:00:00+00:00</published><updated>2025-12-24T00:00:00+00:00</updated><id>https://laveeshb.com/blog/2025/12/building-mcp-server-for-azure-logic-apps</id><content type="html" xml:base="https://laveeshb.com/blog/2025/12/building-mcp-server-for-azure-logic-apps/"><![CDATA[<p>I built an <a href="https://github.com/laveeshb/logicapps-mcp" target="_blank">MCP server</a> for <a href="https://learn.microsoft.com/azure/logic-apps/logic-apps-overview" target="_blank">Azure Logic Apps</a> that lets AI assistants like Claude and GitHub Copilot query workflows, run history, and more. <a href="https://modelcontextprotocol.io/" target="_blank">Model Context Protocol</a> is the glue that makes this possible — it gives AI tools a structured way to discover and call external services.</p>

<p>Here’s how the server works and some design decisions along the way.</p>

<p><strong>Contents:</strong></p>
<ul>
  <li><a href="#the-architecture">The Architecture</a></li>
  <li><a href="#tool-registration">Tool Registration</a></li>
  <li><a href="#authentication-without-sdks">Authentication Without SDKs</a></li>
  <li><a href="#calling-azure-resource-manager">Calling Azure Resource Manager</a></li>
  <li><a href="#handling-both-skus">Handling Both SKUs</a></li>
  <li><a href="#what-can-you-do-with-it">What Can You Do With It?</a></li>
  <li><a href="#try-it">Try It</a></li>
</ul>

<h2 id="the-architecture">The Architecture</h2>

<div class="architecture-diagram">
  <table>
    <tr>
      <td><span class="box">AI Assistant</span></td>
      <td><span class="arrow">⟷</span></td>
      <td><span class="box">MCP Server</span></td>
      <td><span class="arrow">⟷</span></td>
      <td><span class="box">Azure Logic Apps</span></td>
    </tr>
    <tr>
      <td></td>
      <td class="label">stdio</td>
      <td></td>
      <td class="label">REST API</td>
      <td></td>
    </tr>
  </table>
</div>

<p>Here’s the entry point:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Server</span><span class="p">(</span>
  <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">logicapps-mcp</span><span class="dl">"</span><span class="p">,</span> <span class="na">version</span><span class="p">:</span> <span class="dl">"</span><span class="s2">0.1.0</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">capabilities</span><span class="p">:</span> <span class="p">{</span> <span class="na">tools</span><span class="p">:</span> <span class="p">{}</span> <span class="p">}</span> <span class="p">}</span>
<span class="p">);</span>

<span class="nx">registerTools</span><span class="p">(</span><span class="nx">server</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">transport</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">StdioServerTransport</span><span class="p">();</span>
<span class="k">await</span> <span class="nx">server</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">transport</span><span class="p">);</span>
</code></pre></div></div>

<p>MCP uses <code class="language-plaintext highlighter-rouge">stdio</code> for communication — the AI assistant spawns the server process and communicates via <code class="language-plaintext highlighter-rouge">stdin/stdout</code>. No HTTP server, no ports to configure.</p>

<h2 id="tool-registration">Tool Registration</h2>

<p>Each tool is defined with a JSON schema that describes its parameters:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nl">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">list_run_history</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Get the run history for a workflow</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">inputSchema</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">properties</span><span class="p">:</span> <span class="p">{</span>
      <span class="nl">subscriptionId</span><span class="p">:</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">logicAppName</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">top</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">number</span><span class="dl">"</span><span class="p">,</span> <span class="nx">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Number of runs (default: 25)</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">filter</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span> <span class="nx">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">OData filter</span><span class="dl">"</span> <span class="p">}</span>
    <span class="p">},</span>
    <span class="nx">required</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">subscriptionId</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">logicAppName</span><span class="dl">"</span><span class="p">]</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The AI reads these schemas and knows exactly what parameters to provide. When it calls a tool, the server validates the input and executes the corresponding function.</p>

<h2 id="authentication-without-sdks">Authentication Without SDKs</h2>

<p>Rather than pulling in the Azure SDK (which adds significant weight), the server uses Azure CLI tokens directly:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">getAzureCliToken</span><span class="p">():</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">exec</span><span class="p">(</span><span class="dl">"</span><span class="s2">az account get-access-token --query accessToken -o tsv</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">return</span> <span class="nx">result</span><span class="p">.</span><span class="nx">stdout</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This keeps dependencies minimal and leverages the authentication the user already has. If you’re logged into Azure CLI, the MCP server just works.</p>

<h2 id="calling-azure-resource-manager">Calling Azure Resource Manager</h2>

<p>All Logic Apps operations go through the ARM REST API. The HTTP client is a thin wrapper that handles auth injection and pagination:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">armRequest</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">path</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">cloud</span> <span class="o">=</span> <span class="nx">getCloudEndpoints</span><span class="p">();</span>
  <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">getAccessToken</span><span class="p">();</span>

  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">cloud</span><span class="p">.</span><span class="nx">resourceManager</span><span class="p">}${</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span>
    <span class="p">}</span>
  <span class="p">});</span>

  <span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">()</span> <span class="k">as</span> <span class="nx">T</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The server supports Azure Public, Government, and China clouds — just swap the endpoint configuration.</p>

<h2 id="handling-both-skus">Handling Both SKUs</h2>

<p>Logic Apps comes in two flavors: Consumption (serverless, single workflow) and Standard (App Service-based, multiple workflows). The API paths differ:</p>

<ul>
  <li><strong>Consumption</strong>: <code class="language-plaintext highlighter-rouge">/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Logic/workflows/{name}</code></li>
  <li><strong>Standard</strong>: <code class="language-plaintext highlighter-rouge">/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{name}/hostruntime/runtime/webhooks/workflow/api/management/workflows/{workflow}</code></li>
</ul>

<p>Each tool detects the SKU and routes to the appropriate API.</p>

<h2 id="what-can-you-do-with-it">What Can You Do With It?</h2>

<p>Once configured, you can ask your AI assistant things like:</p>

<ul>
  <li>“List all Logic Apps in my subscription”</li>
  <li>“Show me failed runs from the last 24 hours”</li>
  <li>“What’s the definition of the order-processing workflow?”</li>
  <li>“Get the trigger callback URL for the HTTP trigger”</li>
</ul>

<p>The server exposes 18 read-only tools:</p>

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th>Tools</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Discovery</td>
      <td><code class="language-plaintext highlighter-rouge">list_subscriptions</code>, <code class="language-plaintext highlighter-rouge">list_logic_apps</code>, <code class="language-plaintext highlighter-rouge">list_workflows</code></td>
    </tr>
    <tr>
      <td>Definitions</td>
      <td><code class="language-plaintext highlighter-rouge">get_workflow_definition</code>, <code class="language-plaintext highlighter-rouge">get_workflow_swagger</code>, <code class="language-plaintext highlighter-rouge">list_workflow_versions</code></td>
    </tr>
    <tr>
      <td>Triggers</td>
      <td><code class="language-plaintext highlighter-rouge">get_workflow_triggers</code>, <code class="language-plaintext highlighter-rouge">get_trigger_history</code>, <code class="language-plaintext highlighter-rouge">get_trigger_callback_url</code></td>
    </tr>
    <tr>
      <td>Run History</td>
      <td><code class="language-plaintext highlighter-rouge">list_run_history</code>, <code class="language-plaintext highlighter-rouge">get_run_details</code>, <code class="language-plaintext highlighter-rouge">get_run_actions</code></td>
    </tr>
    <tr>
      <td>Debugging</td>
      <td><code class="language-plaintext highlighter-rouge">get_action_repetitions</code>, <code class="language-plaintext highlighter-rouge">get_scope_repetitions</code>, <code class="language-plaintext highlighter-rouge">get_action_request_history</code>, <code class="language-plaintext highlighter-rouge">get_expression_traces</code></td>
    </tr>
    <tr>
      <td>Connections</td>
      <td><code class="language-plaintext highlighter-rouge">get_connections</code></td>
    </tr>
    <tr>
      <td>Diagnostics</td>
      <td><code class="language-plaintext highlighter-rouge">get_host_status</code></td>
    </tr>
  </tbody>
</table>

<h2 id="try-it">Try It</h2>

<p>To get started:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install</span> <span class="nt">-g</span> github:laveeshb/logicapps-mcp
</code></pre></div></div>

<p>Add it to Claude Desktop or VS Code Copilot, and you can start querying your Logic Apps with natural language.</p>

<p>The code is on <a href="https://github.com/laveeshb/logicapps-mcp" target="_blank">GitHub</a> — contributions welcome.</p>]]></content><author><name>Laveesh Bansal</name></author><category term="azure" /><category term="mcp" /><category term="logic-apps" /><category term="typescript" /><category term="ai" /><summary type="html"><![CDATA[How I built an MCP server that lets AI assistants interact with Azure Logic Apps.]]></summary></entry><entry><title type="html">What’s New in Logic Apps MCP v0.2: Debugging Tools</title><link href="https://laveeshb.com/blog/2025/12/logic-apps-mcp-debugging-tools/" rel="alternate" type="text/html" title="What’s New in Logic Apps MCP v0.2: Debugging Tools" /><published>2025-12-24T00:00:00+00:00</published><updated>2025-12-24T00:00:00+00:00</updated><id>https://laveeshb.com/blog/2025/12/logic-apps-mcp-debugging-tools</id><content type="html" xml:base="https://laveeshb.com/blog/2025/12/logic-apps-mcp-debugging-tools/"><![CDATA[<p>I just shipped v0.2.0 of the <a href="https://github.com/laveeshb/logicapps-mcp" target="_blank">Logic Apps MCP server</a> with 5 new debugging tools. The original release had 18 tools for inspecting workflows and run history. This update focuses on making troubleshooting easier.</p>

<p>Here’s what’s new.</p>

<p><strong>Contents:</strong></p>
<ul>
  <li><a href="#search-runs-without-odata">Search Runs Without OData</a></li>
  <li><a href="#get-actual-action-inputs-and-outputs">Get Actual Action Inputs and Outputs</a></li>
  <li><a href="#test-connections">Test Connections</a></li>
  <li><a href="#get-connection-details">Get Connection Details</a></li>
  <li><a href="#access-workflow-versions">Access Workflow Versions</a></li>
  <li><a href="#updated-tool-summary">Updated Tool Summary</a></li>
</ul>

<h2 id="search-runs-without-odata">Search Runs Without OData</h2>

<p>The original <code class="language-plaintext highlighter-rouge">list_run_history</code> tool supported OData filters, but constructing the right filter string takes effort. The new <code class="language-plaintext highlighter-rouge">search_runs</code> tool exposes friendly parameters:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nl">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">search_runs</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">description</span><span class="p">:</span>
    <span class="dl">"</span><span class="s2">Search run history with friendly parameters instead of raw OData filter syntax.</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">inputSchema</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">properties</span><span class="p">:</span> <span class="p">{</span>
      <span class="nl">status</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">enum</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">Succeeded</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Failed</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Cancelled</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Running</span><span class="dl">"</span><span class="p">],</span>
        <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Filter by run status</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="nx">startTime</span><span class="p">:</span> <span class="p">{</span>
        <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
        <span class="nx">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Filter runs starting after this ISO timestamp</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="nx">endTime</span><span class="p">:</span> <span class="p">{</span>
        <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
        <span class="nx">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Filter runs starting before this ISO timestamp</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="nx">clientTrackingId</span><span class="p">:</span> <span class="p">{</span>
        <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
        <span class="nx">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Filter by correlation/tracking ID</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">},</span>
    <span class="p">},</span>
  <span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now you can ask: “Find all failed runs from the last hour” and the AI doesn’t need to construct <code class="language-plaintext highlighter-rouge">$filter=status eq 'Failed' and startTime ge '2025-12-24T10:00:00Z'</code>.</p>

<p>Under the hood, it builds the OData filter for you:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">filterParts</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">status</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">filterParts</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">`status eq '</span><span class="p">${</span><span class="nx">status</span><span class="p">}</span><span class="s2">'`</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">startTime</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">filterParts</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">`startTime ge </span><span class="p">${</span><span class="nx">startTime</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">endTime</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">filterParts</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">`startTime le </span><span class="p">${</span><span class="nx">endTime</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">filter</span> <span class="o">=</span> <span class="nx">filterParts</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">?</span> <span class="nx">filterParts</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">"</span><span class="s2"> and </span><span class="dl">"</span><span class="p">)</span> <span class="p">:</span> <span class="kc">undefined</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="get-actual-action-inputs-and-outputs">Get Actual Action Inputs and Outputs</h2>

<p>The existing <code class="language-plaintext highlighter-rouge">get_run_actions</code> tool returns action metadata, but the actual inputs and outputs are behind separate URLs (<code class="language-plaintext highlighter-rouge">inputsLink</code>, <code class="language-plaintext highlighter-rouge">outputsLink</code>). The new <code class="language-plaintext highlighter-rouge">get_action_io</code> tool follows those links:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nl">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">get_action_io</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">description</span><span class="p">:</span>
    <span class="dl">"</span><span class="s2">Get the actual input/output content for a run action. Fetches the content from inputsLink/outputsLink URLs. Essential for debugging data issues.</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">inputSchema</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">properties</span><span class="p">:</span> <span class="p">{</span>
      <span class="nl">type</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">enum</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">inputs</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">outputs</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">both</span><span class="dl">"</span><span class="p">],</span>
        <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Which content to fetch (default: both)</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="c1">// ... standard params: subscriptionId, resourceGroupName, etc.</span>
    <span class="p">},</span>
  <span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The implementation fetches the action metadata, then follows the content links:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Fetch inputs if requested</span>
<span class="k">if</span> <span class="p">((</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">inputs</span><span class="dl">"</span> <span class="o">||</span> <span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">both</span><span class="dl">"</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">action</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">inputsLink</span><span class="p">?.</span><span class="nx">uri</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">result</span><span class="p">.</span><span class="nx">inputs</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetchContentLink</span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">inputsLink</span><span class="p">.</span><span class="nx">uri</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Fetch outputs if requested</span>
<span class="k">if</span> <span class="p">((</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">outputs</span><span class="dl">"</span> <span class="o">||</span> <span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">both</span><span class="dl">"</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">action</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">outputsLink</span><span class="p">?.</span><span class="nx">uri</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">result</span><span class="p">.</span><span class="nx">outputs</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetchContentLink</span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">outputsLink</span><span class="p">.</span><span class="nx">uri</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The content link URLs include SAS tokens, so no additional auth is needed. This is essential for debugging — when a workflow fails, you want to see what data was actually passed to each action, not just that the action ran.</p>

<h2 id="test-connections">Test Connections</h2>

<p>Logic Apps use API connections for external services (SQL, SharePoint, etc.). When things break, the first question is often: “Is the connection still valid?”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nl">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">test_connection</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">description</span><span class="p">:</span>
    <span class="dl">"</span><span class="s2">Test if an API connection is valid and healthy. Checks connection status and attempts to validate using test links if available.</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">inputSchema</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">properties</span><span class="p">:</span> <span class="p">{</span>
      <span class="nl">subscriptionId</span><span class="p">:</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">resourceGroupName</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">connectionName</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
    <span class="p">},</span>
    <span class="nx">required</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">subscriptionId</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">resourceGroupName</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">connectionName</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The tool checks the connection status and, if test links are available, actually calls them:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// If status is Connected, it's valid</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">details</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">Connected</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">result</span><span class="p">.</span><span class="nx">isValid</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
  <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// If there are test links, try to use them</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">details</span><span class="p">.</span><span class="nx">testLinks</span> <span class="o">&amp;&amp;</span> <span class="nx">details</span><span class="p">.</span><span class="nx">testLinks</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">testLink</span> <span class="o">=</span> <span class="nx">details</span><span class="p">.</span><span class="nx">testLinks</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">method</span><span class="p">:</span> <span class="nx">testLink</span><span class="p">.</span><span class="nx">method</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span> <span class="p">},</span>
  <span class="p">});</span>

  <span class="k">if</span> <span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">result</span><span class="p">.</span><span class="nx">isValid</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
    <span class="nx">result</span><span class="p">.</span><span class="nx">status</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Connected</span><span class="dl">"</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Quick way to rule out (or confirm) connection issues.</p>

<h2 id="get-connection-details">Get Connection Details</h2>

<p>Beyond testing, sometimes you need to inspect a connection’s configuration:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nl">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">get_connection_details</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">description</span><span class="p">:</span>
    <span class="dl">"</span><span class="s2">Get detailed information about a specific API connection including status, configuration, and test links.</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">inputSchema</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">properties</span><span class="p">:</span> <span class="p">{</span>
      <span class="nl">subscriptionId</span><span class="p">:</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">resourceGroupName</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">connectionName</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
    <span class="p">},</span>
    <span class="nx">required</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">subscriptionId</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">resourceGroupName</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">connectionName</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Returns the full connection metadata including API type, status, creation time, parameter values, and test links.</p>

<h2 id="access-workflow-versions">Access Workflow Versions</h2>

<p>Logic Apps (Consumption SKU) keeps a version history. The new <code class="language-plaintext highlighter-rouge">get_workflow_version</code> tool retrieves a specific version’s definition:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nl">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">get_workflow_version</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">description</span><span class="p">:</span>
    <span class="dl">"</span><span class="s2">Get a specific historical version's definition. Only available for Consumption Logic Apps. Use list_workflow_versions to see available versions.</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">inputSchema</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">properties</span><span class="p">:</span> <span class="p">{</span>
      <span class="nl">subscriptionId</span><span class="p">:</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">resourceGroupName</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">logicAppName</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span> <span class="p">},</span>
      <span class="nx">versionId</span><span class="p">:</span> <span class="p">{</span> <span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span> <span class="nx">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Version ID (from list_workflow_versions)</span><span class="dl">"</span> <span class="p">},</span>
    <span class="p">},</span>
    <span class="nx">required</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">subscriptionId</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">resourceGroupName</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">logicAppName</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">versionId</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Useful when you need to compare what changed between versions, or understand what the workflow looked like at a specific point in time.</p>

<h2 id="updated-tool-summary">Updated Tool Summary</h2>

<p>The server now has 23 tools:</p>

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th>Tools</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Discovery</td>
      <td><code class="language-plaintext highlighter-rouge">list_subscriptions</code>, <code class="language-plaintext highlighter-rouge">list_logic_apps</code>, <code class="language-plaintext highlighter-rouge">list_workflows</code></td>
    </tr>
    <tr>
      <td>Definitions</td>
      <td><code class="language-plaintext highlighter-rouge">get_workflow_definition</code>, <code class="language-plaintext highlighter-rouge">get_workflow_swagger</code>, <code class="language-plaintext highlighter-rouge">list_workflow_versions</code>, <code class="language-plaintext highlighter-rouge">get_workflow_version</code></td>
    </tr>
    <tr>
      <td>Triggers</td>
      <td><code class="language-plaintext highlighter-rouge">get_workflow_triggers</code>, <code class="language-plaintext highlighter-rouge">get_trigger_history</code>, <code class="language-plaintext highlighter-rouge">get_trigger_callback_url</code></td>
    </tr>
    <tr>
      <td>Run History</td>
      <td><code class="language-plaintext highlighter-rouge">list_run_history</code>, <code class="language-plaintext highlighter-rouge">get_run_details</code>, <code class="language-plaintext highlighter-rouge">get_run_actions</code>, <code class="language-plaintext highlighter-rouge">search_runs</code></td>
    </tr>
    <tr>
      <td>Debugging</td>
      <td><code class="language-plaintext highlighter-rouge">get_action_io</code>, <code class="language-plaintext highlighter-rouge">get_action_repetitions</code>, <code class="language-plaintext highlighter-rouge">get_scope_repetitions</code>, <code class="language-plaintext highlighter-rouge">get_action_request_history</code>, <code class="language-plaintext highlighter-rouge">get_expression_traces</code></td>
    </tr>
    <tr>
      <td>Connections</td>
      <td><code class="language-plaintext highlighter-rouge">get_connections</code>, <code class="language-plaintext highlighter-rouge">get_connection_details</code>, <code class="language-plaintext highlighter-rouge">test_connection</code></td>
    </tr>
    <tr>
      <td>Diagnostics</td>
      <td><code class="language-plaintext highlighter-rouge">get_host_status</code></td>
    </tr>
  </tbody>
</table>

<p>The focus for this release was debugging workflows. With these tools, an AI assistant can now help you:</p>

<ul>
  <li>Search for problematic runs without writing OData</li>
  <li>Inspect what data actually flowed through each action</li>
  <li>Verify connections are healthy</li>
  <li>Compare workflow versions to spot changes</li>
</ul>

<p>If you’re already using the server, update with <code class="language-plaintext highlighter-rouge">npm update -g logicapps-mcp</code>. If not, check out the <a href="https://github.com/laveeshb/logicapps-mcp" target="_blank">repo</a> to get started.</p>]]></content><author><name>Laveesh Bansal</name></author><category term="azure" /><category term="mcp" /><category term="logic-apps" /><category term="typescript" /><category term="ai" /><category term="debugging" /><summary type="html"><![CDATA[New tools for searching runs, inspecting action I/O, testing connections, and accessing workflow history.]]></summary></entry><entry><title type="html">Welcome to My Blog</title><link href="https://laveeshb.com/blog/2025/12/welcome-to-my-blog/" rel="alternate" type="text/html" title="Welcome to My Blog" /><published>2025-12-24T00:00:00+00:00</published><updated>2025-12-24T00:00:00+00:00</updated><id>https://laveeshb.com/blog/2025/12/welcome-to-my-blog</id><content type="html" xml:base="https://laveeshb.com/blog/2025/12/welcome-to-my-blog/"><![CDATA[<p>Welcome to my blog! After years of building distributed systems and cloud infrastructure, I’ve finally set up a space to share thoughts, learnings, and the occasional deep dive into technical topics.</p>

<h2 id="what-to-expect">What to Expect</h2>

<p>I plan to write about:</p>

<ul>
  <li><strong>Distributed Systems</strong> — Lessons learned from building systems at scale</li>
  <li><strong>Azure &amp; Cloud Architecture</strong> — Patterns, anti-patterns, and war stories</li>
  <li><strong>AI Infrastructure</strong> — How AI is changing the way we build software</li>
  <li><strong>Open Source</strong> — Projects I’m working on and contributions to the community</li>
</ul>

<h2 id="why-now">Why Now?</h2>

<p>After spending years building cloud infrastructure, I figured it might be worth writing down some of the things I’ve learned along the way. Writing helps me think more clearly, and maybe some of these posts will help someone else avoid the same mistakes I’ve made.</p>

<h2 id="stay-tuned">Stay Tuned</h2>

<p>I’ll try to post regularly. No promises on frequency, but I’ll aim for quality over quantity.</p>

<p>Thanks for reading!</p>]]></content><author><name>Laveesh Bansal</name></author><category term="general" /><category term="intro" /><category term="blogging" /><summary type="html"><![CDATA[First post on my blog. Here's what I plan to write about.]]></summary></entry></feed>