<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lovee Jain</title>
    <description>The latest articles on DEV Community by Lovee Jain (@lovee_jain).</description>
    <link>https://dev.to/lovee_jain</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3047101%2Fe1f24ea1-d0da-4384-a949-8ea8f16c5267.jpg</url>
      <title>DEV Community: Lovee Jain</title>
      <link>https://dev.to/lovee_jain</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lovee_jain"/>
    <language>en</language>
    <item>
      <title>Extending MCP Agents with the AWS MCP Server: Why Python ADK Shines?!</title>
      <dc:creator>Lovee Jain</dc:creator>
      <pubDate>Tue, 27 Jan 2026 03:55:16 +0000</pubDate>
      <link>https://dev.to/aws-builders/extending-mcp-agents-with-the-aws-mcp-server-why-python-adk-shines-3kd9</link>
      <guid>https://dev.to/aws-builders/extending-mcp-agents-with-the-aws-mcp-server-why-python-adk-shines-3kd9</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfz7go8qsuy9sonumxje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfz7go8qsuy9sonumxje.png" alt="Image generated with Chat-GPT showing power of MCP servers with ADK" width="800" height="533"&gt;&lt;/a&gt;So if you have read &lt;a href="https://medium.com/@LoveeJain/chaining-mcp-servers-with-gemini-adk-the-typescript-story-425a380407fe" rel="noopener noreferrer"&gt;Part 1: Chaining MCP Servers with Gemini ADK — The TypeScript Story!&lt;/a&gt;, you already know the pain I had when trying to use AWS MCP Server with ADK in Typescript — the type conversions that ADK does when listing the tools from AWS MCP server have type mismatches, due to which it is unable to register the tool we need for our next step.&lt;/p&gt;

&lt;p&gt;But fear not, I have submitted a &lt;a href="https://github.com/google/adk-js/pull/75" rel="noopener noreferrer"&gt;PR&lt;/a&gt; for the fix and until is approved and merged🤞🏻, let’s continue with the Python version.&lt;/p&gt;

&lt;p&gt;For those who missed the context, here’s a TLDR;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We are creating the standard local MCP server that gives us weather of any US location.&lt;/li&gt;
&lt;li&gt;However, for this work it really needs the latitude/longitude of the place, which we will get from Google Maps MCP Server.&lt;/li&gt;
&lt;li&gt;Finally for this part, we will publish the weather update to an AWS SNS Topic which is subscribed by our email — so we get the direct weather update!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And we call all these MCP servers from Google’s Agent Development Kit (ADK).&lt;/p&gt;

&lt;p&gt;Are you ready?&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;We are using the same weather MCP server that we created in Part 1: &lt;a href="https://medium.com/@LoveeJain/chaining-mcp-servers-with-gemini-adk-the-typescript-story-425a380407fe" rel="noopener noreferrer"&gt;Chaining MCP Servers with Gemini ADK — The TypeScript Story!&lt;/a&gt; Find highlighted &lt;em&gt;Create the weather MCP server in the post for instructions&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/docs/tools/inspector" rel="noopener noreferrer"&gt;MCP Inspector&lt;/a&gt; — for checking out tools offered by different MCP servers.&lt;/li&gt;
&lt;li&gt;Finally ADK — this time with Python, so you need to have Python 3.10 or later and &lt;code&gt;pip&lt;/code&gt; for installing packages. Just install ADK by running the following command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install google-adk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create ADK Project and use it as an MCP Client:
&lt;/h2&gt;

&lt;p&gt;It is super easy to get started with ADK when you are using Python. You can find the documentation &lt;a href="https://google.github.io/adk-docs/get-started/python/#create-an-agent-project" rel="noopener noreferrer"&gt;here&lt;/a&gt; or follow the steps below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let’s create the a ADK project:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adk create weather_adk_python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a basic agent that you can chat with. Feel free to test and play.&lt;/p&gt;

&lt;p&gt;But for now we will move on and start creating MCP toolsets so that we can use our local weather MCP server and remote Google Maps MCP server! You can replace all the code with the following sections.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, let’s add some imports:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Next, let’s define the weather MCP toolset:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_weather = McpToolset(
    connection_params=StdioConnectionParams(
        server_params=StdioServerParameters(
            command="node",
            args=["local/path/to/weather_mcp_adk_python/weather_mcp_server/build/index.js"], # Add the correct local path to your MCP server build
        ),
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Next, let’s define Google Maps MCP toolset. Please note: You can find the instructions to get the Google Maps API Key from &lt;a href="https://medium.com/@LoveeJain/chaining-mcp-servers-with-gemini-adk-the-typescript-story-425a380407fe" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google_maps_api_key = os.environ.get("GOOGLE_MAPS_API_KEY")
if not google_maps_api_key:
    raise ValueError("GOOGLE_MAPS_API_KEY environment variable is not set")

get_coordinates = McpToolset(
    connection_params=StdioConnectionParams(
        server_params=StdioServerParameters(
            command='npx',
            args=[
                "-y",
                "@modelcontextprotocol/server-google-maps",
            ],
            env={
                "GOOGLE_MAPS_API_KEY": google_maps_api_key
            },
        ),
    ), tool_filter=["maps_geocode"]
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, define the &lt;code&gt;root_agent&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root_agent = LlmAgent(
    model='gemini-2.5-flash',
    name='root_agent',
    description='A helpful assistant for finding the coordinates and telling weather in a US city/place.' ,
    instruction=(
        "You can use 'get_coordinates' tool to get the coordinates of a US location. " 
        "Once you have the coordinates, you can pass it to 'get_weather' tool to get the weather of that US location."
    ),
    tools=[get_weather, get_coordinates]
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, this brings you to parity with the TS version we created in Part 1. That means it will chain your MCP calls and give you the weather update for any US location without you having to look for lat/long of that place. Let’s try it — with UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adk web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo92tc526eghwamgkktes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo92tc526eghwamgkktes.png" alt="Chaining MCP servers in ADK" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works like cake! Let’s add some icing to it — let’s send this weather update to your email! How do we do that? Well, first we create an AWS SNS Topic and subscribe to it. And then use the AWS MCP server to publish the weather updates to the topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create AWS Topic and Subscription:
&lt;/h2&gt;

&lt;p&gt;I want to mention something important before we go ahead into some clickops. Here’s the documentation on &lt;a href="https://awslabs.github.io/mcp/" rel="noopener noreferrer"&gt;AWS MCP servers&lt;/a&gt; and I highly recommend inspecting the &lt;a href="https://awslabs.github.io/mcp/servers/amazon-sns-sqs-mcp-server" rel="noopener noreferrer"&gt;AWS SNS-SQS MCP Server&lt;/a&gt; first so that we know the capabilities. You can do so by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Please Note:&lt;/strong&gt; The authorization between the MCP server and your AWS accounts are performed with AWS profile you setup on the host. If you have multiple AWS profiles in your system and you would like to use a different profile than the default one, you will need to set AWS_PROFILE in the Environment Variables before connecting to the server or you can run the inspector with the profile set:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_PROFILE=myprofile npx @modelcontextprotocol/inspector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open the inspector where you can then list tools and explore more:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ja33xi7aw1t420wz70o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ja33xi7aw1t420wz70o.png" alt="Inspecting tools in AWS SNS-SQS MCP server with MCP inspector&amp;lt;br&amp;gt;
" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’d find it interesting that the AWS SNS-SQS MCP Server offers tools to create SNS Topics, subscriptions and even confirm the subscription via MCP too! However, for our example we will do these steps manually and regardless, you cannot confirm the email subscription directly using the tool because it requires a token that is only available to the user.&lt;/p&gt;

&lt;p&gt;But, we will use the publish tool within ADK to publish the weather updates to the topic that we have subscribed.&lt;/p&gt;

&lt;p&gt;So let’s create the topic and subscribe using clickops.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once you have logged in, navigate to SNS and create a new standard topic: &lt;code&gt;weather&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidv0s5spc3vxi8sbx99o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidv0s5spc3vxi8sbx99o.png" alt="Creating a new Topic&amp;lt;br&amp;gt;
" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Please Note&lt;/strong&gt;: You need to tag this &lt;strong&gt;topic&lt;/strong&gt; with &lt;code&gt;mcp_server_version&lt;/code&gt; because only &lt;code&gt;mcp_server_version&lt;/code&gt; tagged resources can be accessed by the MCP Server as a security measure. FYI, it’s value is not validated, it works as long as the tag is set to some value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a new Subscription: Select the right topic, type as &lt;code&gt;Email&lt;/code&gt; and put down your email in the enpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Soon, you will get an email to confirm the subscription, confirm it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And done ✅. Copy the Topic ARN as you will need it in the next steps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Use AWS MCP Server in ADK:
&lt;/h2&gt;

&lt;p&gt;Let’s get back to our ADK and modify it. First, we will define some variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TOPIC_ARN = "arn:aws:sns:ap-southeast-2:123456789:weather"
AWS_REGION = "ap-southeast-2"
AWS_PROFILE = "myprofile"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we create an MCP toolset for AWS SNS and just expose the publish tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publish_sns = McpToolset(
    connection_params=StdioConnectionParams(
        server_params=StdioServerParameters(
            command="uvx",
            args=["awslabs.amazon-sns-sqs-mcp-server@latest"],
            env={
                "AWS_PROFILE": AWS_PROFILE
            }
        ),
    ),
    tool_filter=["publish"],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you inspected this tool earlier, you will find that you need to pass in the Topic ARN and the AWS Region as arguments, it cannot pick them up from the environment variables. And hence we will use the power of instructions and description in our root agent so that it picks up the right ARN and region without asking user to put those in.&lt;/p&gt;

&lt;p&gt;Replace the &lt;code&gt;root_agent&lt;/code&gt; part with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root_agent = LlmAgent(
    model='gemini-2.5-flash',
    name='root_agent',
    description='A helpful assistant for finding the coordinates, telling weather in a US city/place and publishing the weather summary to SNS topic.',
    instruction=(
        "You can use 'get_coordinates' tool to get the coordinates of a US location. " 
        "Once you have the coordinates, you can pass it to 'get_weather' tool to get the weather of that US location. " 
        "Once prompted, you can use 'publish_sns' tool to send the weather summary as message or if the user can do any outdoor activities based on the weather to SNS topic. "
        f"Use {TOPIC_ARN} as topic ARN and {AWS_REGION} as region. Do not ask the user for it." 
    ),
    tools=[get_weather, get_coordinates, publish_sns],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it — let’s give it a go!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adk web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbo5bsa6e1vrmvjjg3x2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbo5bsa6e1vrmvjjg3x2n.png" alt="ADK with 3 different MCP tools" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapz8l4irmtzi6y9w1ouh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapz8l4irmtzi6y9w1ouh.png" alt="Weather update from AWS SNS Topic on Email" width="800" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hoorayyy!!! It works! We can now get some weather summaries and whether we can do some outdoor activities as per the weather in a particular US city — right into our inbox!&lt;/p&gt;

&lt;h2&gt;
  
  
  Save Yourself the Headache:
&lt;/h2&gt;

&lt;p&gt;While implementing this was so much fun, it did come with a bit of a headache. So let me help you save it!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not forget to use the &lt;strong&gt;right Topic ARN and AWS Region&lt;/strong&gt; — they are key or else you will get stuck in this infinite loop of not being able to publish to your Topic without a proper error message.&lt;/li&gt;
&lt;li&gt;It is also the power of &lt;strong&gt;writing good instructions and descriptions&lt;/strong&gt; of your agent that can yield different results, after all, it is an LLM and it is non-deterministic and can never give you the same response.&lt;/li&gt;
&lt;li&gt;Do not forget to &lt;strong&gt;tag&lt;/strong&gt; your AWS Topic — else you cannot publish!&lt;/li&gt;
&lt;li&gt;If you have multiple AWS Profiles, I have found it really hard to get consistent results — sometimes it somehow picks the wrong or the default profile even after we are setting the right one in the environment variables. So I actually have also tried setting it up before calling &lt;code&gt;adk web&lt;/code&gt; like this: &lt;code&gt;export AWS_PROFILE=&amp;lt;profile_name&amp;gt;&lt;/code&gt;. It is also recommended in the ADK docs to &lt;strong&gt;set environment variables before running &lt;code&gt;adk web&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Last, but not the least, if your toolsets also timeout when initialising, you can &lt;strong&gt;optionally set a higher timeout&lt;/strong&gt; in your &lt;code&gt;connection_params&lt;/code&gt; to prevent it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the &lt;a href="https://github.com/Lovee93/weather_mcp_adk_python" rel="noopener noreferrer"&gt;Github repo&lt;/a&gt; with all the code you need!&lt;/p&gt;

&lt;p&gt;And if you are thinking what I am thinking, yes I potentially want to deploy all this and create something production-ready! So there’s a chance of next part to this series.&lt;/p&gt;

&lt;p&gt;Meanwhile happy experimenting!! 🔍&lt;/p&gt;

</description>
      <category>awsmcp</category>
      <category>aws</category>
      <category>adk</category>
      <category>agents</category>
    </item>
    <item>
      <title>API Destinations for Private Endpoints: From Oversight to Insight!</title>
      <dc:creator>Lovee Jain</dc:creator>
      <pubDate>Sun, 13 Apr 2025 12:24:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/api-destinations-for-private-endpoints-from-oversight-to-insight-4l8i</link>
      <guid>https://dev.to/aws-builders/api-destinations-for-private-endpoints-from-oversight-to-insight-4l8i</guid>
      <description>&lt;p&gt;I made a mistake! I was tasked to do a discovery on how we can sync some data from a monolith to a new micro-service and I came up with 3 main solutions and a fallback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monolith → Event Bridge → API Destination → Exposed endpoint in the microservice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcd043rj6rmegycpetvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcd043rj6rmegycpetvj.png" alt="API Destination" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monolith → Event Bridge → SQS ← Polling from microservice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F738x71t3kkaoja8hwehn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F738x71t3kkaoja8hwehn.png" alt="SQS Polling" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monolith → Event Bridge → SNS Topic → Exposed endpoint in the microservice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntzcltw0jn6yjzxx2c58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntzcltw0jn6yjzxx2c58.png" alt="SNS Topic" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(Fallback) Microservice → Event Bridge → SQS → Lambda → Exposed Endpoint in the service or directly update the database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcii8lsjgmte7s7ptdu2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcii8lsjgmte7s7ptdu2q.png" alt="SQS and Lambda" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Justification and Rationale
&lt;/h2&gt;

&lt;p&gt;Now I have 2 main reasons on how and why I came up with these solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The monolith was already configured to send events to an event bridge&lt;/li&gt;
&lt;li&gt;One of the requirements was to keep the monolith and microservice loosely coupled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now if you look at the options, the choice is obvious, API Destination!! But let’s still dig a bit deeper as to why?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;With API Destination, all I need to do is deploy some IaaS, expose an endpoint in the service and done!&lt;/li&gt;
&lt;li&gt;With SQS, even easier, but polling means the service is always looking for an event. The data to be synced was not something that was changed frequently, but when it did, it was important to reflect the change and the service behaviour quickly.
Furthermore even though the setup was easy, it had the complexity of batch processing, deleting messages after they were processed (yes, you need to do that manually with SQS), and the risk of the same message being delivered more than once, so service needs to be idempotent. This was all just too much for a little sync, 😕 .&lt;/li&gt;
&lt;li&gt;With SNS topics, confirming message subscription was tricky! Furthermore, event structure was not JSON but plain text and that also meant that we get tightly coupled with AWS message structure, if that changed, we will have to adjust on our end too.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I know what you are thinking, why did I keep SQS + Lambda as a fallback? To be honest, I regret it too, I came to a point where we half-implemented this with tears in my eyes. The fact that I need to maintain another lambda, just to pass on some event payload to a service that already has an exposed endpoint was a major deterrent. The infrastructure, the code — seemed just to much effort for a little sync task. Besides, how many Lambda functions do you have sitting around, forgotten and unmaintained? We often spin up Lambdas for various tasks because they’re so easy to create — but that also means many of them linger, unused and unmanaged.&lt;/p&gt;

&lt;p&gt;And don’t forget, because I didn’t, we also needed a dead letter queue (DLQ) in this — just in case the service is unavailable to process the data. With lambda, the re-drive solution would have become even more painful!&lt;/p&gt;

&lt;p&gt;And hence, with all these considerations, API destination looked the most viable option!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pratfall
&lt;/h2&gt;

&lt;p&gt;With discovery done, solution finalised, initiative created and stories refined, we went into implementation phase in the next quarter and had a, guess what??&lt;/p&gt;

&lt;p&gt;A pratfall!!&lt;/p&gt;

&lt;p&gt;I made a mistake, I forgot my service was internal and API destinations back then didn’t support private endpoints. This was a disaster, as it meant we couldn’t use API Destinations and mid-quarter we had to pivot, putting the initiative at risk. The advice was to use SQS + Lambda, something I really didn’t want to do, but finally had to.&lt;/p&gt;

&lt;p&gt;And then suddenly, with a flash of lightning and a rumble of thunder, the team at AWS:ReInvent 2024 unveiled an exciting new feature for EventBridge — private API integrations! This &lt;a href="https://aws.amazon.com/blogs/aws/securely-share-aws-resources-across-vpc-and-account-boundaries-with-privatelink-vpc-lattice-eventbridge-and-step-functions/" rel="noopener noreferrer"&gt;announcement&lt;/a&gt; brought the whole initiative back on track, and we lived happily ever after…😁 Let’s learn more about how this feature saved the day!&lt;/p&gt;

&lt;h2&gt;
  
  
  How did API destinations for private endpoints save the day?
&lt;/h2&gt;

&lt;p&gt;If you look at this now, you’d feel overwhelmed, but trust me even though it looks a lot, you literally just create a couple of infra resources once and you’ll be sorted for the lifetime!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqrek7tfphn5eiqx9m4v1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqrek7tfphn5eiqx9m4v1.png" alt="API Destination with Private Endpoint" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s understand each of these resources in detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal service and security group&lt;/strong&gt;: We know that our internal service could be in a private subnet and not accessible to the public. But it should always have a security group attached to its load balancer. Now we need to make sure that the &lt;em&gt;security group ingress allows traffic from within the VPC or the VPC in which the API destination will exist&lt;/em&gt;. Once we open that route, we need to create a resource gateway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqug8dih13ge8rr1uoabo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqug8dih13ge8rr1uoabo.png" alt="Example of an internal ALB for an internal service" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Gateway&lt;/strong&gt;: The resource gateway provides a point of ingress into a VPC (and subnets) so that clients can access resources in that VPC. The resources that will become accessible are actually defined in a Resource Configuration that is associated with the gateway. One Resource Gateway can make multiple resources available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06krbhiw18shl3nfz6ml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06krbhiw18shl3nfz6ml.png" alt="Configure the Resource Gateway to access private subnet resources via security group" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Configuration&lt;/strong&gt;: This is where you provide the set of resources which will be accessible through a particular resource gateway. Now your resource could either be referenced by an IP Address, or DNS Name or an AWS ARN.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F724ek1ulaxzb10fr7v6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F724ek1ulaxzb10fr7v6l.png" alt="Creating resource configuration for our internal service domain" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our case, let’s say the internal service has a domain name, then we can provide that domain name in the configuration. We can also provide port ranges, port 80 for HTTP and port 443 for HTTPS. There is also an optional Share resource configuration setting that you can create to share resources across AWS accounts or through AWS organisations. You can read more about it &lt;a href="https://aws.amazon.com/blogs/aws/securely-share-aws-resources-across-vpc-and-account-boundaries-with-privatelink-vpc-lattice-eventbridge-and-step-functions/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcovxkzjqtmdwi1k176fi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcovxkzjqtmdwi1k176fi.png" alt="Sharing ports 80 and 443, skipping share resource configuration" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Destination and connection&lt;/strong&gt;: Like any other AWS services, there’s a connection wrapper around the actual resource which is API destination. And if you look at the console, you’ll find Connections have updated! Yes, because that is where we will now configure connection to our private API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3zhe8cyxr09cl7g7hmo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3zhe8cyxr09cl7g7hmo.png" alt="Configure invocation as Private API in the API destination connection" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We configure the invocation type as Private and attach the above resource configuration. Next we specify the authorisation type and that’s it! ✅. Once created, this will also establish a Service Network Association.&lt;/p&gt;

&lt;p&gt;Next, we create the API destination, specify the destination endpoint and method, configure any rate limiting and use the above existing connection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybly9c2hiyn1ark8p71t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybly9c2hiyn1ark8p71t.png" alt="Configuring API Destination" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service Network Association&lt;/strong&gt;: Now, a service network is a logical grouping of services that can communicate with each other securely and efficiently without requiring complex networking setups. However, when you configure your API Destination connection for a Private API and specify the resource configuration, it automatically creates a service network association for the event bridge service in your resource configuration. This Service Network Association is created and managed by AWS, so there’s no extra effort required on your part!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy85kphoq830oyn8v1y7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy85kphoq830oyn8v1y7p.png" alt="Service Network Association created by AWS" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event Rule&lt;/strong&gt;: Now I am assuming you already have an event bus setup, so we are going to skip that part and focus on creating the event rule that will publish events to your API destination.&lt;/p&gt;

&lt;p&gt;So let’s define the event rule detail, we first specify the name, description and the event bus. Next if you specify the rule type as a &lt;em&gt;Rule with an event pattern&lt;/em&gt;, then, this rule will be triggered when an event matching the pattern occurs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xc3zj8q4qwzn5ozy3eq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xc3zj8q4qwzn5ozy3eq.png" alt="Creating event rule with pattern" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to define the pattern. We can either choose a predefined pattern that matches a certain type of event from a certain service, or you can create a custom pattern that is suited to your service. When creating a pattern only specify the fields that you are using for matching. You can read more about Event Patterns in the &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-pattern.html" rel="noopener noreferrer"&gt;AWS EvenBridge Patterns User Guide&lt;/a&gt;. But here’s a sample event for your reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "detail": {
    "event_type": ["SomeDataUpdate"]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Furthermore, there are multiple &lt;a href="https://dev.tocomparison%20operators"&gt;comparison operators&lt;/a&gt; that you can choose from to compare your event pattern, we can simply use Prefix matching for our example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47lqnt0ig3hox26p54r5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47lqnt0ig3hox26p54r5.png" alt="Building custom event pattern with specific fields for matching" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once happy with the event pattern, we can then configure the target as our API destination and specify the existing API destination that we created above. You can also pass additional headers or query parameters to your target. And finally just like any other resource, you also need to create an IAM role that will allow EventBridge to send events to your target, in this case the API Destination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p6b0q368g9rjsnj4ey1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p6b0q368g9rjsnj4ey1.png" alt="Adding API destination as the target in the Event rule" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although it is optional, it is highly recommended that you add a DLQ to your event rule so that if your service is unavailable to consume events they can be later on replayed or re-drived to your service when it is back online.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsn9gd7w6xnz2zs4po21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsn9gd7w6xnz2zs4po21.png" alt="Adding a DLQ" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Review the rule and hit Save!&lt;/p&gt;

&lt;p&gt;Now is the time to sit back and relax, your sync from the monolith to your internal micro-service is now active!&lt;/p&gt;

&lt;h2&gt;
  
  
  Extra Nugget:
&lt;/h2&gt;

&lt;p&gt;If you want to make this experience even better, you can leverage an Input Transformer at the Event Bridge. Input transformers can help transform the input event from the event bridge before it gets to the target so that it is received in a certain expected format! Pretty easy to set up, and AWS provides a comprehensive guide to using them: &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html" rel="noopener noreferrer"&gt;Amazon EventBridge input transformation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju5pudg9hvscmoe8uf9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju5pudg9hvscmoe8uf9m.png" alt="Configure the input transformer in Event Rule Additional Settings" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5i9v66fm9aml68nj49cx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5i9v66fm9aml68nj49cx.png" alt="Example input transformer" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Treat:
&lt;/h2&gt;

&lt;p&gt;Here’s the final treat for you that might save you some time! &lt;a href="https://github.com/Lovee93/api-destination-private" rel="noopener noreferrer"&gt;Github Repo&lt;/a&gt; to CloudFormation Templates for deploying API Destination resources for private endpoints.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
