<?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: Jack McGuire</title>
    <description>The latest articles on DEV Community by Jack McGuire (@jackmcguire1).</description>
    <link>https://dev.to/jackmcguire1</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%2F911842%2Ffd0d2285-cfd4-496f-9b9c-293e98a96409.jpeg</url>
      <title>DEV Community: Jack McGuire</title>
      <link>https://dev.to/jackmcguire1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jackmcguire1"/>
    <language>en</language>
    <item>
      <title>Batch publishing Twitch chatroom messages - with AWS Lambda Durable Multi-Step Executions</title>
      <dc:creator>Jack McGuire</dc:creator>
      <pubDate>Thu, 11 Dec 2025 15:11:21 +0000</pubDate>
      <link>https://dev.to/aws-builders/batch-publishing-twitch-chatroom-messages-with-aws-lambda-durable-multi-step-executions-2a30</link>
      <guid>https://dev.to/aws-builders/batch-publishing-twitch-chatroom-messages-with-aws-lambda-durable-multi-step-executions-2a30</guid>
      <description>&lt;p&gt;Hi all!&lt;/p&gt;

&lt;p&gt;AWS have announced yet another AWS Lambda feature &lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/12/lambda-durable-multi-step-applications-ai-workflows/" rel="noopener noreferrer"&gt;release&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In this post I will be covering a use-case for a Lambda that utilises durable multi-step executions. I will be covering overall thoughts, gotchas and summary. &lt;/p&gt;

&lt;h2&gt;
  
  
  What are Durable AWS Lambda Functions?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;AWS Lambda announces durable functions, enabling developers to build reliable multi-step applications and AI workflows within the Lambda developer experience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Durable functions automatically checkpoint progress, suspend execution for up to one year during long-running tasks, and recover from failures - all without requiring you to manage additional infrastructure or write custom state management and error handling code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use-Case
&lt;/h2&gt;

&lt;p&gt;As you may or may not know, I run a Twitch Extension &lt;a href="https://dashboard.twitch.tv/extensions/e93cf8730nd11z7gepkly2gry5kv8k" rel="noopener noreferrer"&gt;'Stat-Milestones'&lt;/a&gt;. In short the Twitch Extension renders a widget on streamer's profiles, this widget displays a multitude of social goals information to viewers [Followers, Subscribers, Hypetrains, Charities, etc]&lt;/p&gt;

&lt;p&gt;Twitch Extensions have the ability [user-permitted] to publish messages to the public chatroom of a Twitch streamer via the Twitch API.&lt;/p&gt;

&lt;p&gt;Generally as a viewer you will see messages from Twitch bots/extensions that provide information like reminders to follower the streamer you're viewing or links to external profiles, etc.&lt;/p&gt;

&lt;p&gt;For a long while, I have been intending to setup a pipeline to publish daily messages to Twitch streamers chatrooms. With the intention to remind streamers to update their Twitch goals and Stat-Milestones metrics!&lt;br&gt;
&lt;em&gt;Other communication methods are available, but their chatroom will definitely catch their attention!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With the announcement of AWS Lambda Durable Multi-Step Executions, I figure this is the perfect opportunity to have a play with the new feature and hopefully provide some insightful information if you're thinking about adopting Durable executions yourself!&lt;/p&gt;
&lt;h2&gt;
  
  
  Why not just use Step-Functions?
&lt;/h2&gt;

&lt;p&gt;To note, I use AWS SAM [YAML] to deploy infrastructure for my Twitch Extension.&lt;/p&gt;

&lt;p&gt;Personally setting up infrastructure for a step-function takes the above average complexity for IaC, see &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-state-machine-using-sam.html" rel="noopener noreferrer"&gt;Create a Step Functions state machine using AWS SAM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With Durable AWS Lambdas, the need to develop state definitions is now instead done all in-code and the abundant step execution features are found directly within a new tab on the Lambda dashboard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Amazon States Language is a JSON-based, structured language used to define your state machine, a collection of states, that can do work (Task states), determine which states to transition to next (Choice states), stop an execution with an error (Fail states), and so on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Durable Lambda Implementation
&lt;/h2&gt;

&lt;p&gt;In this section I will detail how I created an application to publishing daily reminder messages to Twitch chatrooms.&lt;/p&gt;

&lt;p&gt;In order to publish daily reminders to Twitch streamer chatrooms, I thought about how I require to break this down into notable Durable execution steps.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User Discovery&lt;/li&gt;
&lt;li&gt;Batch Publishing of reminder messages&lt;/li&gt;
&lt;li&gt;Result Aggregatation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Discovery
&lt;/h3&gt;

&lt;p&gt;The goal of this step is to discover users from a Mongo collection who have granted the permissions to allow my Twitch Extension to publish to their chatroom AND still have a valid auth token.&lt;/p&gt;
&lt;h3&gt;
  
  
  Batch publishing of messages
&lt;/h3&gt;

&lt;p&gt;In this step, the intention is to construct reminder message(s) and publish them to users found in the discovery phase.&lt;/p&gt;

&lt;p&gt;I already have an existing pipeline [sqs -&amp;gt; lambda] in the us-east-1 region that processes messages from a SQS queue and constructs API requests to Twitch to publish the messages to the chatrooms directly.&lt;/p&gt;

&lt;p&gt;Simply this execution step just requires to construct SQS Messages and publish them to an external Queue (living in us-east-1 not us-east-2!)&lt;/p&gt;
&lt;h3&gt;
  
  
  Aggregating Results
&lt;/h3&gt;

&lt;p&gt;This execution step is to aggregate the final results of the prior steps... user discovery, message publishing metrics, etc.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;With the help of AI tools, I set out to create a Python run-time Durable AWS Lambda function that implements the discovery, batch and result aggregations into seperate steps.&lt;/p&gt;

&lt;p&gt;Utilising Steps and Map context processing, we can seperate out the discovery and results into singular steps.&lt;/p&gt;

&lt;p&gt;For the Batch Publishing of messages, we can do something clever and utilise the context map. This example demonstrates how durable functions combine map() operations with chunking and rate limiting to handle large-scale data processing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;My example Durable lambda code can be found &lt;a href="https://gist.github.com/jackmcguire1/697c33c9dc1a470868df1ac765a21c37" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The file has to be compiled into a zip with the external packages&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requirements.txt
pymongo&amp;gt;=4.10.1
boto3&amp;gt;=1.35.0
aws-durable-execution-sdk-python&amp;gt;=0.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dashboard Breakdown
&lt;/h2&gt;

&lt;p&gt;The new Durable execution tab found on the Lambda dashboard brings, somewhat familiar widgets to step-functions.&lt;/p&gt;

&lt;p&gt;The ability to inspect the inputs/outputs of step executions is a big win for future applications built with Lambda, reduces the need to deep-dive on logs to inspect payloads and results between steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Pictures
&lt;/h2&gt;

&lt;p&gt;Here find examples of the inspected durable execution and nested step execution details.&lt;/p&gt;

&lt;p&gt;I have not supplied user discovery step due to GDPR reasons, however please find screenshots of durable step executions&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%2F6kal13yi4qcgi4xy8h4x.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%2F6kal13yi4qcgi4xy8h4x.png" alt="Singular Durable Execution" width="800" height="128"&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%2Fcu9hxttgktsazo29gku5.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%2Fcu9hxttgktsazo29gku5.png" alt="Overall Step Input/Output" width="800" height="352"&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%2Ff2qz3d1p612ap4s5td07.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%2Ff2qz3d1p612ap4s5td07.png" alt="Durable Execution Steps Overview" width="800" height="392"&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%2Fct364xb5b8jc7p6gn4gb.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%2Fct364xb5b8jc7p6gn4gb.png" alt="Output of Batch Message Publishing" width="548" height="748"&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%2Fhp42lgw1w8gcs3khcrwq.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%2Fhp42lgw1w8gcs3khcrwq.png" alt="Output of Aggregated Results" width="800" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Outcome
&lt;/h2&gt;

&lt;p&gt;The end goal of course is to post reminders&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%2Fdme9mon5erp5a8zvw0lq.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%2Fdme9mon5erp5a8zvw0lq.png" alt="Example Twitch Chat" width="674" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;limited region availability - as of writing&lt;/li&gt;
&lt;li&gt;limited run-time support [Node, Python] - as of writing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://stat-milestones.dev" rel="noopener noreferrer"&gt;https://stat-milestones.dev&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dashboard.twitch.tv/extensions/e93cf8730nd11z7gepkly2gry5kv8k" rel="noopener noreferrer"&gt;Twitch Extension About Page&lt;/a&gt;&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>serverless</category>
      <category>aws</category>
      <category>durable</category>
    </item>
    <item>
      <title>Making an MCP server for the twitch-cli to aid developer experience</title>
      <dc:creator>Jack McGuire</dc:creator>
      <pubDate>Mon, 12 May 2025 12:10:50 +0000</pubDate>
      <link>https://dev.to/jackmcguire1/making-an-mcp-server-for-the-twitch-cli-to-aid-developer-experience-2ho8</link>
      <guid>https://dev.to/jackmcguire1/making-an-mcp-server-for-the-twitch-cli-to-aid-developer-experience-2ho8</guid>
      <description>&lt;h2&gt;
  
  
  Simulate Twitch Event-Sub Events via Cursor with an MCP Server for the &lt;code&gt;twitch-cli&lt;/code&gt;.
&lt;/h2&gt;

&lt;p&gt;When building Twitch integrations—like chat bots, overlays, or EventSub-powered features—testing can be painful without real user actions triggering events. That's where the &lt;a href="https://github.com/twitchdev/twitch-cli" rel="noopener noreferrer"&gt;Twitch CLI&lt;/a&gt; and &lt;a href="https://github.com/jlowin/fastmcp" rel="noopener noreferrer"&gt;fastmcp&lt;/a&gt; come in.&lt;/p&gt;

&lt;p&gt;In this post, I'll show how I set up a local MCP server using &lt;code&gt;fastmcp&lt;/code&gt;, and how I use it to trigger events like a Twitch follow using &lt;code&gt;twitch-cli&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an MCP Server?
&lt;/h2&gt;

&lt;p&gt;In the context of tools like Cursor (and possibly other AI IDEs), an &lt;strong&gt;MCP server&lt;/strong&gt; lets you define custom “tools” in code—these tools can then be called by the AI as part of its workflow.&lt;/p&gt;

&lt;p&gt;It’s a simple protocol: your server runs locally and exposes functions with structured inputs/outputs over Server-Sent Events (SSE). When the AI tool calls one of your functions, it hits your server and gets the result.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using &lt;code&gt;fastmcp&lt;/code&gt; to Build a Local MCP Server for Cursor AI + Twitch CLI Integration
&lt;/h1&gt;

&lt;p&gt;If you're using an AI coding tool like &lt;a href="https://www.cursor.so" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;, you might've come across their support for &lt;strong&gt;MCP (Model Context Protocol) servers&lt;/strong&gt;—a way to define your own tools that the AI can invoke directly while coding.&lt;/p&gt;

&lt;p&gt;In this post, I’ll show how I’m using &lt;a href="https://github.com/jlowin/fastmcp" rel="noopener noreferrer"&gt;&lt;code&gt;fastmcp&lt;/code&gt;&lt;/a&gt; to set up a local MCP server, which I’ve wired up to Twitch CLI. With this, I can write prompts in Cursor like:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;simulate a twitch follow eventsub event, for user 1234567890&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;I have the following in my repo:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;./cursor/mcp.json&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This config tells cursor to use my local MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"twitchCli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:8000/sse/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"disabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"autoApprove"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MCP Server code
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;server.py&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shlex&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;twitch-cli&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trigger_twitch_follow_event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trigger_twitch_follow_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Trigger a Twitch follow event for a specific user ID.

    Args:
        user_id (str): The Twitch user ID to trigger the follow event for

    Returns:
        Dict[str, Any]: Command output or error message
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DOMAIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TWITCH_EVENTSUB_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;twitch event trigger follow -t &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -s &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -F &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/twitch-eventsub&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Split the command into a list of arguments
&lt;/span&gt;        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shlex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Execute the command and capture output
&lt;/span&gt;        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stderr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;return_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalledProcessError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stderr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;return_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running locally
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;export DOMAIN=example.com&lt;br&gt;
export TWITCH_EVENTSUB_SECRET=xxx&lt;br&gt;
python server.py&lt;br&gt;
ask your ide AI code agent “Trigger a Twitch follow event for user 12345”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Images
&lt;/h1&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%2Fmb9qxybl5szx0e7seslk.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%2Fmb9qxybl5szx0e7seslk.png" alt=" " width="501" height="678"&gt;&lt;/a&gt;&lt;br&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%2F4szl2b0tu4sii8x6vwwm.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%2F4szl2b0tu4sii8x6vwwm.png" alt=" " width="347" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;This workflow has been game-changing for building Twitch integrations. I can now prototype and test my EventSub logic without writing manual cli commands, rather just describe the desired event and targeted Twitch user I want in the Cursor AI Chat agent, and it triggers the action via my local tool.&lt;/p&gt;

&lt;p&gt;If you're building tools that benefit from programmatic APIs, local scripts, or devops integrations, fastmcp + Cursor’s tool-calling support is an incredibly powerful setup.**&lt;/p&gt;

</description>
      <category>cli</category>
      <category>mcp</category>
      <category>tooling</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Wheel Of Fortune</title>
      <dc:creator>Jack McGuire</dc:creator>
      <pubDate>Sun, 21 Aug 2022 18:07:00 +0000</pubDate>
      <link>https://dev.to/jackmcguire1/wheel-of-fortune-3521</link>
      <guid>https://dev.to/jackmcguire1/wheel-of-fortune-3521</guid>
      <description>&lt;h2&gt;
  
  
  Overview of My Submission
&lt;/h2&gt;

&lt;p&gt;A Wheel Of Fortune project that utilises REDIS in order to  maintain wheel segment prize allocation and distribution.&lt;/p&gt;

&lt;p&gt;This project comes with a GRPC Server, Client and Admin client built in GO to maintain wheels.&lt;/p&gt;

&lt;p&gt;A VUEJS website built with GRPC, ENVOY was created to spin the virtual Wheel Of Fortune!&lt;/p&gt;

&lt;h2&gt;
  
  
  Submission Category:
&lt;/h2&gt;

&lt;p&gt;Wacky Wildcards&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BU-vebFk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atoeo4ttm73zhnpggu7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BU-vebFk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atoeo4ttm73zhnpggu7z.png" alt="Wheel Of Fortune vueJS app" width="880" height="849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uFSAHrHT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzvtgz3hd80llwwwobse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uFSAHrHT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzvtgz3hd80llwwwobse.png" alt="Frontend - backend GRPC" width="82" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IBnISThS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vh7fzrsfauz3r6q79m45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IBnISThS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vh7fzrsfauz3r6q79m45.png" alt="Server listening!" width="406" height="44"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--95F2MkLG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xow8dbsl2fqppg6a9p0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--95F2MkLG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xow8dbsl2fqppg6a9p0g.png" alt="Wheel spins - CLI" width="864" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Language Used
&lt;/h2&gt;

&lt;p&gt;GO, Javascript, LUA&lt;/p&gt;

&lt;h2&gt;
  
  
  Link to Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jackmcguire1"&gt;
        jackmcguire1
      &lt;/a&gt; / &lt;a href="https://github.com/jackmcguire1/wheeloffortune"&gt;
        wheeloffortune
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      grpc - wheel of fortune svc
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
REDIS - Wheel Of Fortune&lt;/h1&gt;
&lt;p&gt;A simualted Wheel Of Fortune backed by Redis exposed by a GRCP Server &amp;amp; Client.&lt;/p&gt;
&lt;h2&gt;
How it works&lt;/h2&gt;
&lt;p&gt;This project simulates a Wheel Of Fortune by making use of Redis
to maintain the segments of a wheel i.e. prize allocation and distribution&lt;/p&gt;
&lt;p&gt;The server makes use of Redis LUA scripts to synchronously execute user
spins and exposes an API via GRPC to maintain and spin and wheels of fortune!&lt;/p&gt;
&lt;h3&gt;
How the data is stored:&lt;/h3&gt;
&lt;p&gt;Segments and Operational Flags of a wheel of fortune are stored in
Redis Keys.&lt;/p&gt;
&lt;h4&gt;
Wheel Of Fortune Creation&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;When a wheel is CREATED the following keys are generated:&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;
wheel of fortune segment(s)&lt;/h5&gt;
&lt;p&gt;For each defined segment within a wheel of fortune with an allocated prize
a REDIS key like &lt;code&gt;'wheel:{wheelname}:segment:{index}'&lt;/code&gt;, data is stored like &lt;code&gt;INCRBY {KEY} {VALUE}&lt;/code&gt; in a TX&lt;/p&gt;
&lt;h4&gt;
wheel of fortune Enabled Status&lt;/h4&gt;
&lt;p&gt;To set the enable status…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jackmcguire1/wheeloffortune"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Additional Resources / Info
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;REDIS for wheel of fortune management&lt;/li&gt;
&lt;li&gt;GO to expose a GRPC API&lt;/li&gt;
&lt;li&gt;ENVOY is used to provide a HTTP TO GRPC proxy&lt;/li&gt;
&lt;li&gt;VUEJS for front-end webapp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jack McGuire &lt;a href="https://github.com/jackmcguire1"&gt;https://github.com/jackmcguire1&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Check out &lt;a href="https://redis.io/docs/stack/get-started/clients/#high-level-client-libraries"&gt;Redis OM&lt;/a&gt;, client libraries for working with Redis as a multi-model database.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Use &lt;a href="https://redis.info/redisinsight"&gt;RedisInsight&lt;/a&gt; to visualize your data in Redis.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sign up for a &lt;a href="https://redis.info/try-free-dev-to"&gt;free Redis database&lt;/a&gt;.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>redishackathon</category>
    </item>
  </channel>
</rss>
