<?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: Anton Umnikov</title>
    <description>The latest articles on DEV Community by Anton Umnikov (@antonum).</description>
    <link>https://dev.to/antonum</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%2F455473%2Fdd8e9abc-3d81-48ea-8b80-38de488dcd80.jpeg</url>
      <title>DEV Community: Anton Umnikov</title>
      <link>https://dev.to/antonum</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/antonum"/>
    <language>en</language>
    <item>
      <title>Minimalistic MCP Server in bash script</title>
      <dc:creator>Anton Umnikov</dc:creator>
      <pubDate>Mon, 31 Mar 2025 20:35:47 +0000</pubDate>
      <link>https://dev.to/antonum/minimalistic-mcp-server-in-bash-script-10k5</link>
      <guid>https://dev.to/antonum/minimalistic-mcp-server-in-bash-script-10k5</guid>
      <description>&lt;p&gt;Let's create a minimalistic MCP server as a single, self-sufficient shell script. &lt;/p&gt;

&lt;p&gt;GitHub code for this article: &lt;a href="https://github.com/antonum/mcp-server-bash" rel="noopener noreferrer"&gt;https://github.com/antonum/mcp-server-bash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Model Context Protocol (MPC) is a hot topic nowadays. You can find many YouTube videos and articles guiding you through creating your first MPC server with Python, TypeScript, etc.&lt;/p&gt;

&lt;p&gt;Cool!!! But... All these examples rely on external libraries such as &lt;code&gt;FastMCP&lt;/code&gt; for Python or &lt;code&gt;McpServer&lt;/code&gt; for TypeScript. Essentially hiding the complexity (or the elegance if you will) of the underlying protocol from you.&lt;/p&gt;

&lt;p&gt;For me, it's a great learning opportunity to learn the inner workings of MCP, but you can think of different use cases, such as turning your existing bash scripts into tools for LLM without giving them access to the entire system via bash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic stdio server in bash
&lt;/h2&gt;

&lt;p&gt;First of all - MCP works either via HTTP request or via standard Input/Output. Let's use &lt;code&gt;stdio&lt;/code&gt; here. The bare minimum &lt;code&gt;stdio server&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$line&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can save this file as &lt;code&gt;test.sh&lt;/code&gt; and, in the terminal, run &lt;code&gt;bash test.sh&lt;/code&gt;. The server would just repeat whatever you type until you press Ctrl^C.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSONRPC
&lt;/h2&gt;

&lt;p&gt;Not exactly what we need, but we are getting there... Instead of arbitrary text, MCP use the &lt;code&gt;jsonrpc&lt;/code&gt; protocol. Here are the basic request and response in &lt;code&gt;jsonrpc&lt;/code&gt;:&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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;p&gt;Very simple. &lt;code&gt;"jsonrpc": "2.0"&lt;/code&gt; tells us what specific protocol we are using and &lt;code&gt;"id": 1&lt;/code&gt; is an id of requests that must be matched by the server response.&lt;/p&gt;

&lt;p&gt;Now, let's adapt our server to match the sample request and response above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.method'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.id'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"add"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="c"&gt;# Parse the parameters from the JSON input&lt;/span&gt;
        &lt;span class="nv"&gt;num1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.params[0]'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;num2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.params[1]'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;# Perform the addition&lt;/span&gt;
        &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;num1 &lt;span class="o"&gt;+&lt;/span&gt; num2&lt;span class="k"&gt;))&lt;/span&gt;

        &lt;span class="c"&gt;# Return the result as JSON&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"sum":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$sum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'}}'&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"error":{"code":-32601,"message":"Method not found"}}'&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can test it by running the following in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc": "2.0","method": "add","params": [5, 3],"id": 1}'&lt;/span&gt; | bash test.sh

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"jsonrpc"&lt;/span&gt;:&lt;span class="s2"&gt;"2.0"&lt;/span&gt;,&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"result"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"sum"&lt;/span&gt;:8&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Almost there! We now do have a functioning &lt;code&gt;jsonrps stdio&lt;/code&gt; server that even traps [some of] the errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model Context Protocol (MCP)
&lt;/h2&gt;

&lt;p&gt;There has been nothing specific to MCP so far. Just arbitrary jsonrpc. Let's get MCP working. The lifecycle of MCP server can be described in two phases. Initialization and Operation.&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%2Fj4pe47n79e2ld3eadeui.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%2Fj4pe47n79e2ld3eadeui.png" alt="Phases of MCP" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The init phase messages look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# introductions:
Client: {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0}
Server: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"math","version":"0.0.1"}}}
Client: {"method":"notifications/initialized","jsonrpc":"2.0"}

# gather server capabilities:
Client: {"method":"resources/list","params":{},"jsonrpc":"2.0","id":1}
Server: {"jsonrpc":"2.0","id":1,"result":{"resources":[]}}
Client: {"method":"tools/list","params":{},"jsonrpc":"2.0","id":2}
Server: {"jsonrpc":"2.0","id":2,"result":{"tools":[]}}
Client: {"method":"prompts/list","params":{},"jsonrpc":"2.0","id":3}
Server: {"jsonrpc":"2.0","id":3,"result":{"prompts":[]}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, the client (AI Host such as Claude Desktop or mcphost) and server (your bash script) "shake hands" and inform each other of their capabilities. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hey, I'm Claude AI!&lt;/li&gt;
&lt;li&gt;Nice to meet you! I'm &lt;code&gt;math&lt;/code&gt; server version 0.1.0&lt;/li&gt;
&lt;li&gt;Cool! What are your resources?&lt;/li&gt;
&lt;li&gt;None&lt;/li&gt;
&lt;li&gt;How about tools?&lt;/li&gt;
&lt;li&gt;None&lt;/li&gt;
&lt;li&gt;Prompts?&lt;/li&gt;
&lt;li&gt;nah!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Indeed, there's nothing in our server yet, but nevertheless if only you implement these calls in your server, you'll get a fully functioning "do nothing" server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fully - functioning "do nothing" MCP Server
&lt;/h2&gt;

&lt;p&gt;Here is our updated bash script that speaks MCP and passes the initialization step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.method'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.id'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"initialize"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"math","version":"0.0.1"}}}'&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"notifications/initialized"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        : &lt;span class="c"&gt;#do nothing&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"tools/list"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"tools":[]}}'&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"resources/list"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"resources":[]}}'&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"prompts/list"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"prompts":[]}}'&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"error":{"code":-32601,"message":"Method not found"}}'&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create update the &lt;code&gt;test.sh&lt;/code&gt; with the code above and make sure to add execute permissions to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chmode +x test.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to test it you'll need to either update the existing or create a new configuration file for your LLM host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "mcpServers": {
      "math": {
        "command": "/Users/anton/code/mcp-server-bash/test.sh",
        "args": []
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;a href="https://claude.ai/download" rel="noopener noreferrer"&gt;Claude Desktop&lt;/a&gt; that file would be &lt;code&gt;claude_desktop_config.json&lt;/code&gt;. You can access it via Claude Menu -&amp;gt; Settings -&amp;gt; Developer - Edit Config. For &lt;a href="https://github.com/mark3labs/mcphost" rel="noopener noreferrer"&gt;mcphost&lt;/a&gt; you can specify this file right in the command line - add the JSON code above to the file &lt;code&gt;mcp_bare.json&lt;/code&gt; and add it to the arguments of mcphost alongside with any model that is capable of using tools. I'm using ollama with llama3.1 here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcphost &lt;span class="nt"&gt;-m&lt;/span&gt; ollama:llama3.1:latest &lt;span class="nt"&gt;--config&lt;/span&gt; /Users/anton/code/mcp-server-bash/mcp_bare.json
&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%2F0y6q06ds068oh6jp3rjz.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%2F0y6q06ds068oh6jp3rjz.png" alt="MCP Server with mcphost" width="800" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from the output, we successfully connected to the server and loaded exactly 0 tools. Which is expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a tool to add [two numbers]
&lt;/h2&gt;

&lt;p&gt;And the final step - let's add a tool. This tool would accept two numbers as an input and output the sum of these two numbers. Exactly like we already did in the jsonrpc example above, but now as a proper MCP tool that you can call from the LLM.&lt;/p&gt;

&lt;p&gt;To add a tool, you need to adjust the response to &lt;code&gt;tools/list&lt;/code&gt; method to let the Client know what this tool does and the method signature as well as implement corresponding logic for the &lt;code&gt;tools/call&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Here is the updated response for &lt;code&gt;tools/list&lt;/code&gt;:&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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&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;"tools"&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="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"addition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"addition of two numbers.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Args:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    num1, num2&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&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;"inputSchema"&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;"properties"&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;"num1"&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="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Number1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"num2"&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="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Number2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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;"required"&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="s2"&gt;"num1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"num2"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&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;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;p&gt;and request/response for the addition method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client: {"jsonrpc":"2.0","id":20, "method":"tools/call","params":{"name":"addition","arguments":{"num1":"1","num2":"2"}}}
Server: {"jsonrpc":"2.0","id":20,"result":{"content":[{"type":"text","text":"\n sum of two numbers is 3"}],"isError":false}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Here is the final result that incorporates all we learned. STDIO server, rpcjson payload going back and forth between client and server as valid MCP messages and simple kindergarden level math.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# Parse JSON input using jq&lt;/span&gt;
    &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.method'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.id'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"initialize"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"math","version":"0.0.1"}}}'&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"notifications/initialized"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        : &lt;span class="c"&gt;#do nothing&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"tools/list"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"tools":[{"name":"addition","description":"addition of two numbers.\n\nArgs:\n    num1, num2\n","inputSchema":{"properties":{"num1":{"title":"Number1","type":"string"},"num2":{"title":"Number2","type":"string"}},"required":["num1", "num2"],"type":"object"}}]}}'&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"resources/list"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"resources":[]}}'&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"prompts/list"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"prompts":[]}}'&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"tools/call"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;tool_method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.params.name'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;num1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.params.arguments.num1'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;num2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.params.arguments.num2'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;num1 &lt;span class="o"&gt;+&lt;/span&gt; num2&lt;span class="k"&gt;))&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"result":{"content":[{"type":"text","text":"\n sum of two numbers is '&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$sum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"}],"isError":false}}'&lt;/span&gt;

    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;',"error":{"code":-32601,"message":"Method not found"}}'&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final result:&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%2Faphswhu2uu7jcxldzsnl.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%2Faphswhu2uu7jcxldzsnl.png" alt="Using bash tool with llama and mcphost" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you have it. The fully functioning Model Context Protocol server in bash. No dependencies, no external libraries. Just under 30 lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Model Context Protocol &lt;a href="https://www.anthropic.com/news/model-context-protocol" rel="noopener noreferrer"&gt;https://www.anthropic.com/news/model-context-protocol&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;mcphost &lt;a href="https://github.com/mark3labs/mcphost" rel="noopener noreferrer"&gt;https://github.com/mark3labs/mcphost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;jsonrpc specification: &lt;a href="https://www.jsonrpc.org/specification" rel="noopener noreferrer"&gt;https://www.jsonrpc.org/specification&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>bash</category>
      <category>ai</category>
    </item>
    <item>
      <title>Federated SQL queries to AWS S3 with InterSystems IRIS</title>
      <dc:creator>Anton Umnikov</dc:creator>
      <pubDate>Mon, 07 Dec 2020 17:17:04 +0000</pubDate>
      <link>https://dev.to/intersystems/federated-sql-queries-to-aws-s3-with-intersystems-iris-3hoo</link>
      <guid>https://dev.to/intersystems/federated-sql-queries-to-aws-s3-with-intersystems-iris-3hoo</guid>
      <description>&lt;p&gt;IRIS External Table is an InterSystems Community Open Source Project, that allows you to use files, stored in the local file system and cloud object storage such as AWS S3 as SQL Tables. &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%2Fi%2Fdkr4b0tqn1spmnyf8kck.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%2Fi%2Fdkr4b0tqn1spmnyf8kck.png" alt="Alt Text" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can be found on Github  &lt;a href="https://github.com/intersystems-community/IRIS-ExternalTable" rel="noopener noreferrer"&gt;https://github.com/intersystems-community/IRIS-ExternalTable&lt;/a&gt; Open Exchange &lt;a href="https://openexchange.intersystems.com/package/IRIS-External-Table" rel="noopener noreferrer"&gt;https://openexchange.intersystems.com/package/IRIS-External-Table&lt;/a&gt; and is included in InterSystems Package Manager ZPM.&lt;/p&gt;

&lt;p&gt;To instal External Table from GitHub use:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/antonum/IRIS-ExternalTable.git
iris session iris
USER&amp;gt;set sc = ##class(%SYSTEM.OBJ).LoadDir("&amp;lt;path-to&amp;gt;/IRIS-ExternalTable/src", "ck",,1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To install with ZPM Package Manager use:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USER&amp;gt;zpm "install external-table"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Working with local files
&lt;/h2&gt;

&lt;p&gt;Let’s create a simple file that looks like this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a1,b1
a2,b2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Open your favorite editor and create the file or just use a command line in linux/mac:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo $'a1,b1\na2,b2' &amp;gt; /tmp/test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In IRIS SQL Create table to represent this file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table test (col1 char(10),col2 char(10))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Convert table to use external storage:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CALL EXT.ConvertToExternal(
    'test',
    '{
        "adapter":"EXT.LocalFile",
        "location":"/tmp/test.txt",
        "delimiter": ","
    }')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And finally – query the table:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select * from test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If everything works out as expected you should see output like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;col1    col2
a1  b1
a2  b2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now get back to the editor, change the content of the file and rerun the SQL query. Ta-Da!!! You are reading new values from your local file in SQL.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;col1    col2
a1  b1
a2  b99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Reading data from S3
&lt;/h2&gt;

&lt;p&gt;At you can get access to constantly updating data on COVID, stored by AWS in the public data lake.&lt;/p&gt;

&lt;p&gt;Let’s try to access one of the data sources in this data lake: &lt;code&gt;s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you have AWS command line tool installed – you can repeat the steps below. If not – skip straight to SQL part. You don’t need anything AWS – specific to be installed on your machine to follow along with SQL part.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aws s3 ls s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/
2020-12-04 17:19:10     510572 us-states.csv

$ aws s3 cp s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/us-states.csv .
download: s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/us-states.csv to ./us-states.csv

$ head us-states.csv 
date,state,fips,cases,deaths
2020-01-21,Washington,53,1,0
2020-01-22,Washington,53,1,0
2020-01-23,Washington,53,1,0
2020-01-24,Illinois,17,1,0
2020-01-24,Washington,53,1,0
2020-01-25,California,06,1,0
2020-01-25,Illinois,17,1,0
2020-01-25,Washington,53,1,0
2020-01-26,Arizona,04,1,0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So we have a file with a fairly simple structure. Five delimited fields.&lt;/p&gt;

&lt;p&gt;To expose this S3 folder as the External Table – first, we need to create a "regular" table with the desired structure:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- create external table
create table covid_by_state (
    "date" DATE, 
    "state" VARCHAR(20),
    fips INT,
    cases INT,
    deaths INT
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note that some field names like “Date” are reserved words in IRIS SQL and need to be surrounded by double quotes.&lt;br&gt;
Then – we need to convert this “regular” table to the “external” table, based on AWS S3 bucket and CSV type.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; -- convert table to external storage
call EXT.ConvertToExternal(
    'covid_by_state',
    '{
    "adapter":"EXT.AWSS3",
    "location":"s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/",
    "type": "csv",
    "delimiter": ",",
    "skipHeaders": 1
    }' 
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you'll look closely - EXT.ExternalTable procedures arguments are table name and then JSON string, containing multiple parameters, such as location to look for files, adapter to use, delimiter etc. Besides AWS S3 External Table supports Azure BLOB storage, Google Cloud Buckets and the local filesystem. GitHub Repo contains references for the syntax and options supported for all the formats.&lt;/p&gt;

&lt;p&gt;And finally – query the table:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- query the table
select top 10 * from covid_by_state order by "date" desc

[SQL]USER&amp;gt;&amp;gt;select top 10 * from covid_by_state order by "date" desc
2.  select top 10 * from covid_by_state order by "date" desc

date    state   fips    cases   deaths
2020-12-06  Alabama 01  269877  3889
2020-12-06  Alaska  02  36847   136
2020-12-06  Arizona 04  364276  6950
2020-12-06  Arkansas    05  170924  2660
2020-12-06  California  06  1371940 19937
2020-12-06  Colorado    08  262460  3437
2020-12-06  Connecticut 09  127715  5146
2020-12-06  Delaware    10  39912   793
2020-12-06  District of Columbia    11  23136   697
2020-12-06  Florida 12  1058066 19176
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It takes understandably more time to query data from the remote table, then it is for the “IRIS native” or global based table, but it is completely stored and updated on the cloud and being pulled into IRIS behind the scenes. &lt;/p&gt;

&lt;p&gt;Let’s explore a couple of more features of the External Table.&lt;/p&gt;

&lt;h2&gt;
  
  
  %PATH and tables, based on multiple files
&lt;/h2&gt;

&lt;p&gt;In our example folder in the bucket contains just one file. More often then not it would have multiple files of the same structure where filename identifies either timestamp or deviceid of some other attribute that we’ll want to use in our queries.&lt;/p&gt;

&lt;p&gt;%PATH field is automatically added to every External Table and contains full path to the file where row was retrieved from.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select top 5 %PATH,* from covid_by_state

%PATH   date    state   fips    cases   deaths
s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/us-states.csv    2020-01-21  Washington  53  1   0
s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/us-states.csv    2020-01-22  Washington  53  1   0
s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/us-states.csv    2020-01-23  Washington  53  1   0
s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/us-states.csv    2020-01-24  Illinois    17  1   0
s3://covid19-lake/rearc-covid-19-nyt-data-in-usa/csv/us-states/us-states.csv    2020-01-24  Washington  53  1   0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can use this %PATH field in your SQL queries as any other field.&lt;/p&gt;

&lt;h2&gt;
  
  
  ETL data to “Regular Tables”
&lt;/h2&gt;

&lt;p&gt;If your task is to load data from S3 into an IRIS table – you can use the External Table as an ETL tool. Just do:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO internal_table SELECT * FROM external_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In our case, if we want to copy COVID data from S3 into the local table:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--create local table
create table covid_by_state_local (
    "date" DATE, 
    "state" VARCHAR(100),
    fips INT,
    cases INT,
    deaths INT
)
--ETL from External to Local table
INSERT INTO covid_by_state_local SELECT TO_DATE("date",'YYYY-MM-DD'),state,fips,cases,deaths FROM covid_by_state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  JOIN between IRIS – native and External table. Federated queries
&lt;/h2&gt;

&lt;p&gt;External Table is an SQL table. It can be joined with other tables, used in subselects and UNIONs. You can even combine the “Regular” IRIS table and two or more external tables from different sources in the same SQL query.&lt;/p&gt;

&lt;p&gt;Try creating a regular table such as matching state names to state codes like Washington – WA. And Join it with our S3-Based table.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table state_codes (name varchar(100), code char(2))
insert into state_codes values ('Washington','WA')
insert into state_codes values ('Illinois','IL')

select top 10 "date", state, code, cases from covid_by_state join state_codes on state=name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Change ‘join’ to ‘left join’ to include rows for which state code is not defined. As you can see - the result is a combination of data from S3 and your native IRIS table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secure access to data
&lt;/h2&gt;

&lt;p&gt;AWS Covid Data Lake is public. Anyone can read data from it without any authentication or authorization. In real life you want access your data in secure way that would prevent strangers from peeking at your files. Full details of AWS Identity and Access Management (IAM) is outside of the scope of this article. But the minimum, you need to know is that you need at least AWS Account Access Key and Secret in order to access private data in your account. &lt;/p&gt;

&lt;p&gt;AWS Uses Account Key/Secret authentication to sign requests. &lt;a href="https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are running IRIS External Table on EC2 instance, the recommended way of dealing with authentication is using EC2 Instance Roles &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html&lt;/a&gt; IRIS External Table would be able to use permissions of that role. No extra setup required.&lt;/p&gt;

&lt;p&gt;On a local/non EC2 instance you need to specify AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY by either specifying environment variables or installing and configuring AWS CLI client.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export AWS_ACCESS_KEY_ID=AKIAEXAMPLEKEY
export AWS_SECRET_ACCESS_KEY=111222333abcdefghigklmnopqrst
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Make sure that the environment variable is visible within your IRIS process. You can verify it by running:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USER&amp;gt;write $system.Util.GetEnviron("AWS_ACCESS_KEY_ID")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It should output the value of the key.&lt;/p&gt;

&lt;p&gt;or install AWS CLI, following instruction here &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html&lt;/a&gt; and run:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;External Table then will be able to read the credentials from aws cli config files. Your interactive shell and IRIS process might be running under different accounts - make sure you run &lt;code&gt;aws configure&lt;/code&gt; under the same account as your IRIS process. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>InterSystems IRIS Deployment Guide with CloudFormation Template</title>
      <dc:creator>Anton Umnikov</dc:creator>
      <pubDate>Wed, 19 Aug 2020 19:35:31 +0000</pubDate>
      <link>https://dev.to/intersystems/intersystems-iris-deployment-guide-with-cloudformation-template-1e87</link>
      <guid>https://dev.to/intersystems/intersystems-iris-deployment-guide-with-cloudformation-template-1e87</guid>
      <description>&lt;h1&gt;
  
  
  InterSystems IRIS Deployment Guide – AWS Partner Network
&lt;/h1&gt;

&lt;p&gt;Version 0.8, Aug 12, 2020&lt;/p&gt;

&lt;p&gt;Anton Umnikov&lt;/p&gt;

&lt;p&gt;Templates Source code is available here: &lt;a href="https://github.com/antonum/AWSIRISDeployment" rel="noopener noreferrer"&gt;https://github.com/antonum/AWSIRISDeployment&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;InterSystems provides the _ &lt;strong&gt;CloudFormation Template&lt;/strong&gt; _ for users to set up their own InterSystems IRIS® data platform according to InterSystems and AWS best practices.&lt;/p&gt;

&lt;p&gt;This guide will detail the steps to deploy the _ &lt;strong&gt;CloudFormation template.&lt;/strong&gt; _&lt;/p&gt;

&lt;p&gt;In this guide, we cover two types of deployments for the InterSystems IRIS _ &lt;strong&gt;CloudFormation template&lt;/strong&gt; _. The first method is highly available using multiple availability zones (AZ) and targeted to production workloads, and the second method is a single availability zone deployment for development and testing workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites and Requirements
&lt;/h2&gt;

&lt;p&gt;In this section, we detail the prerequisites and requirements to run and operate our solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time
&lt;/h3&gt;

&lt;p&gt;The deployment itself takes about &lt;strong&gt;4 minutes&lt;/strong&gt; , but with prerequisites and testing it could take &lt;strong&gt;up to 2 hours&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Product License and Binaries
&lt;/h3&gt;

&lt;p&gt;InterSystems IRIS binaries are available to InterSystems customers via &lt;a href="https://wrc.intersystems.com/" rel="noopener noreferrer"&gt;https://wrc.intersystems.com/&lt;/a&gt;. Login with your WRC credentials and follow the links to Actions -&amp;amp;gt; SW Distributions -&amp;amp;gt; InterSystems IRIS. This Deployment Guide is written for the Red Hat platform of InterSystems IRIS 2020.1 build 217. IRIS binaries file names are of the format &lt;code&gt;ISCAgent-2020.1.0.215.0-lnxrhx64.tar.gz&lt;/code&gt; and &lt;code&gt;IRISHealth-2020.1.0.217.1-lnxrhx64.tar.gz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;InterSystems IRIS license key – you should be able to use your existing InterSystems IRIS license key (iris.key). You can also request an evaluation key via the InterSystems IRIS Evaluation Service: &lt;a href="https://download.intersystems.com/download/register.csp" rel="noopener noreferrer"&gt;https://download.intersystems.com/download/register.csp&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Account
&lt;/h3&gt;

&lt;p&gt;You must have an AWS account set up. If you do not, visit: &lt;a href="https://aws.amazon.com/getting-started/" rel="noopener noreferrer"&gt;https://aws.amazon.com/getting-started/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM Entity for user
&lt;/h3&gt;

&lt;p&gt;Create an IAM user or role. Your IAM user should have a policy that allows AWS CloudFormation actions. Do not use your root account to deploy the CloudFormation template. In addition to AWS CloudFormation actions, IAM users who create or delete stacks will also require additional permissions that depend on the stack template. This deployment requires permissions to all the services listed in the following section.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Reference:&lt;/em&gt; &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM Role for EC2
&lt;/h3&gt;

&lt;p&gt;The CloudFormation template requires an IAM role that allows your EC2 instance to access S3 buckets and put logs into CloudWatch. See Appendix "IAM Policy for EC2 instance" for an example of the policy associated with such role.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Reference:&lt;/em&gt; &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 Bucket
&lt;/h3&gt;

&lt;p&gt;Create an S3 bucket called "my bucket", copy IRIS binaries files and iris.key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUCKET=&amp;lt;my bucket&amp;gt;
aws s3 mb s3://$BUCKET
aws s3 cp ISCAgent-2020.1.0.215.0-lnxrhx64.tar.gz s3://$BUCKET
aws s3 cp IRISHealth-2020.1.0.217.1-lnxrhx64.tar.gz s3://$BUCKET
aws s3 cp iris.key s3://$BUCKET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VPC and Subnets
&lt;/h3&gt;

&lt;p&gt;The template is designed to deploy IRIS into an existing VPC and Subnets. In regions where three or more Availability Zones are available, we recommend creating three private subnets across three different AZ's. Bastion Host should be located in any of the public subnets within the VPC. You can follow the AWS example to create a VPC and Subnets with the CloudFormation template: &lt;a href="https://docs.aws.amazon.com/codebuild/latest/userguide/cloudformation-vpc-template.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/codebuild/latest/userguide/cloudformation-vpc-template.html&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  EC2 Key Pair
&lt;/h3&gt;

&lt;p&gt;To access the EC2 instances provisioned by this template, you will need at least one EC2 Key Pair. Refer to this guide for details: &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knowledge Requirements
&lt;/h3&gt;

&lt;p&gt;Knowledge of the following AWS services is required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html" rel="noopener noreferrer"&gt;Amazon Elastic Compute Cloud (Amazon EC2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Introduction.html" rel="noopener noreferrer"&gt;Amazon Virtual Private Cloud (Amazon VPC)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html" rel="noopener noreferrer"&gt;AWS CloudFormation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html" rel="noopener noreferrer"&gt;AWS Elastic Load Balancing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; S3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Account limit increases will not be required for this deployment.&lt;/p&gt;

&lt;p&gt;More information on proper policy and permissions can be found here: &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Individuals possessing the&lt;/em&gt; &lt;a href="https://aws.amazon.com/certification/?nav=tc&amp;amp;loc=3" rel="noopener noreferrer"&gt;&lt;em&gt;AWS Associate certifications&lt;/em&gt;&lt;/a&gt; &lt;em&gt;should have a sufficient depth of knowledge.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;In this section, we give architecture diagrams of two deployment possibilities, and talk about architecture design choices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-AZ Fault Tolerant Architecture Diagram (Preferred)
&lt;/h3&gt;

&lt;p&gt;In this preferred option, mirrored IRIS instances are situated behind a load balancer in two availability zones to ensure high availability and fault tolerance. In regions with three or more availability zones, the Arbiter node is located in the third AZ.&lt;/p&gt;

&lt;p&gt;Database nodes are located in private subnets. Bastion Host is in a Public subnet within the same VPC.&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%2Fi%2Fm3zai6h8vi4jm9qs25ir.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%2Fi%2Fm3zai6h8vi4jm9qs25ir.png" alt="Alt Text" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Network Load Balancer directs database traffic to the current Primary IRIS node&lt;/li&gt;
&lt;li&gt;Bastion Host allows secure access to the IRIS EC2 instances&lt;/li&gt;
&lt;li&gt;IRIS stores all customer data in encrypted EBS volumes

&lt;ol&gt;
&lt;li&gt;EBS is encrypted and uses the AWS Key Management Service (KMS) managed key&lt;/li&gt;
&lt;li&gt;For regulated workloads where encryption of data in transit is required, you can choose to use the r5n family of instances, since they provide automatic instance-to-instance traffic encryption. IRIS-level traffic encryption is also possible but not enabled by CloudFormation (see the Encrypting Data in Transit section of this guide)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Use of security groups restrict access to the greatest degree possible by only allowing necessary traffic&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Single Instance, Single AZ Architecture (Development and Testing)
&lt;/h3&gt;

&lt;p&gt;InterSystems IRIS can also be deployed in a single Availability Zone for development and evaluation purposes. The data flow and architecture components are the same as the ones highlighted in the previous section. This solution does not provide high availability or fault tolerance and is not suitable for production use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into your AWS account with the IAM entity created in the Prerequisites section with the required permissions to deploy the solution&lt;/li&gt;
&lt;li&gt;Make sure all the Prerequisites, such as VPC, S3 bucket, IRIS binaries and license key are in place&lt;/li&gt;
&lt;li&gt;Click the following link to deploy CloudFormation template (deploys in us-east-1): &lt;a href="https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=InterSystemsIRIS&amp;amp;templateURL=https://isc-tech-validation.s3.amazonaws.com/MirrorCluster.yaml" rel="noopener noreferrer"&gt;https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=InterSystemsIRIS&amp;amp;templateURL=https://isc-tech-validation.s3.amazonaws.com/MirrorCluster.yaml&lt;/a&gt; for multi-AZ, fault tolerant deployment&lt;/li&gt;
&lt;li&gt;In 'Step 1 - Create Stack', press the 'Next' button&lt;/li&gt;
&lt;li&gt;In 'Step 2 - Specify stack details', fill out and adjust CloudFormation parameters depending on your requirements&lt;/li&gt;
&lt;li&gt;Press the 'Next' button&lt;/li&gt;
&lt;li&gt;In 'Step 3 - Configure stack options', enter and adjust optional tags, permissions, and advanced options&lt;/li&gt;
&lt;li&gt;Press the 'Next' button&lt;/li&gt;
&lt;li&gt;Review your CloudFormation configurations&lt;/li&gt;
&lt;li&gt;Press the 'Create Stack' button&lt;/li&gt;
&lt;li&gt;Wait approximately 4 minutes for your CloudFormation template to deploy&lt;/li&gt;
&lt;li&gt;You can verify your deployment has succeeded by looking for a 'CREATE_COMPLETE' status&lt;/li&gt;
&lt;li&gt;If the status is 'CREATE_FAILED', see the troubleshooting section in this guide&lt;/li&gt;
&lt;li&gt;Once deployment succeeds, please carry out Health Checks from this guide&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;In this section, we discuss the InterSystems IRIS default configuration deployed by this guide, general best practices, and options for securing your solution on AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data in Private Subnets
&lt;/h3&gt;

&lt;p&gt;InterSystems IRIS EC2 instances must be placed in Private subnets and accessed only via Bastion Host or by applications via the Load Balancer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encrypting IRIS Data at Rest
&lt;/h3&gt;

&lt;p&gt;On database instances running InterSystems IRIS, data is stored at rest in underlying EBS volumes which are encrypted. This CloudFormation template creates EBS volumes encrypted with the account-default AWS managed Key, named aws/ebs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encrypting IRIS data in transit
&lt;/h3&gt;

&lt;p&gt;This CloudFormation does not secure Client-Server and Instance-to-Instance connections. Should data in transit encryption be required, follow the steps outlined below after the deployment is completed.&lt;/p&gt;

&lt;p&gt;Enabling SSL for SuperServer connections (JDBC/ODBC connections): &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCAS_ssltls#GCAS_ssltls_superserver" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCAS_ssltls#GCAS_ssltls_superserver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Durable multi-AZ configuration traffic between IRIS EC2 instances may need to be encrypted too. This can be achieved either by enabling SSL Encryption for mirroring: &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCAS_ssltls#GCAS_ssltls_mirroring" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCAS_ssltls#GCAS_ssltls_mirroring&lt;/a&gt; or switching to the r5n family of instances which provides automatic encryption of instance-to-instance traffic.&lt;/p&gt;

&lt;p&gt;You can use AWS Certificate Manager (ACM) to easily provision, manage, and deploy Secure Sockets Layer/Transport Layer Security (SSL/TLS) certificates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure access to IRIS Management Portal
&lt;/h3&gt;

&lt;p&gt;By default, the IRIS management portal is accessed only via Bastion Host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logging/Auditing/Monitoring
&lt;/h3&gt;

&lt;p&gt;InterSystems IRIS stores logging information in the messages.log file. CloudFormation does not setup any additional logging/monitoring services. We recommend that you enable structured logging as outlined here: &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=ALOG" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=ALOG&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The CloudFormation template does not install InterSystems IRIS-CloudWatch integration. InterSystems recommends using InterSystems IRIS-CloudWatch integration from &lt;a href="https://github.com/antonum/CloudWatch-IRIS" rel="noopener noreferrer"&gt;https://github.com/antonum/CloudWatch-IRIS&lt;/a&gt;. This enables collection of IRIS metrics and logs from the messages.log file into AWS CloudWatch.&lt;/p&gt;

&lt;p&gt;The CloudFormation template does not enable AWS CloudTrail logs. You can enable CloudTrail logging by navigating to the CloudTrail service console and enabling CloudTrail logs. With CloudTrail, activity related to actions across your AWS infrastructure are recorded as an event in CloudTrail. This helps you enable governance, compliance, and operational and risk auditing of your AWS account.&lt;/p&gt;

&lt;p&gt;_Reference: _&lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;InterSystems recommends monitoring of InterSystems IRIS logs and metrics, and alerting on at least the following indicators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;severity 2 and 3 messages&lt;/li&gt;
&lt;li&gt;license consumption&lt;/li&gt;
&lt;li&gt;disk % full for journals and databases&lt;/li&gt;
&lt;li&gt;Write Daemon status&lt;/li&gt;
&lt;li&gt;Lock Table status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to the above, customers are encouraged to identify their own monitoring and alert metrics and application-specific KPIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sizing/Cost
&lt;/h2&gt;

&lt;p&gt;This guide will create the AWS resources outlined in the &lt;strong&gt;Deployment Assets&lt;/strong&gt;  section of this document. You are responsible for the cost of AWS services used while running this deployment. The minimum viable configuration for an InterSystems IRIS deployment provides high availability and security.&lt;/p&gt;

&lt;p&gt;The template in this guide is using the BYOL (Bring Your Own License) InterSystems IRIS licensing model.&lt;/p&gt;

&lt;p&gt;You can access Pay Per Hour IRIS Pricing at the InterSystems IRIS Marketplace page: &lt;a href="https://aws.amazon.com/marketplace/pp/B07XRX7G6B?qid=1580742435148&amp;amp;sr=0-3" rel="noopener noreferrer"&gt;https://aws.amazon.com/marketplace/pp/B07XRX7G6B?qid=1580742435148&amp;amp;sr=0-3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For details on BYOL pricing, please contact InterSystems at: &lt;a href="https://www.intersystems.com/who-we-are/contact-us/" rel="noopener noreferrer"&gt;https://www.intersystems.com/who-we-are/contact-us/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following AWS assets are required to provide a functional platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 EC2 Instances (including EBS volumes and provisioned IOPS)&lt;/li&gt;
&lt;li&gt;1 Elastic Load Balancer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following table outlines recommendations for EC2 and EBS capacity built into the deployment CloudFormation template, as well as AWS resources costs (Units $/Month).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Workload&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Dev/Test&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Prod Small&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Prod Medium&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Prod Large&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EC2 DB*&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;m5.large&lt;/td&gt;
&lt;td&gt;2 * r5.large&lt;/td&gt;
&lt;td&gt;2 * r5.4xlarge&lt;/td&gt;
&lt;td&gt;2 * r5.8xlarge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EC2 Arbiter*&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;t3.small&lt;/td&gt;
&lt;td&gt;t3.small&lt;/td&gt;
&lt;td&gt;t3.small&lt;/td&gt;
&lt;td&gt;t3.small&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EC2 Bastion*&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;t3.small&lt;/td&gt;
&lt;td&gt;t3.small&lt;/td&gt;
&lt;td&gt;t3.small&lt;/td&gt;
&lt;td&gt;t3.small&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EBS SYS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;gp2 20GB&lt;/td&gt;
&lt;td&gt;gp2 50GB&lt;/td&gt;
&lt;td&gt;io1 512GB 1,000iops&lt;/td&gt;
&lt;td&gt;io1 600GB 2,000iops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EBS DB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;gp2 128GB&lt;/td&gt;
&lt;td&gt;gp2 128GB&lt;/td&gt;
&lt;td&gt;io1 1TB 10,000iops&lt;/td&gt;
&lt;td&gt;io1 4TB 10,000iops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EBS JRN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;gp2 64GB&lt;/td&gt;
&lt;td&gt;gp2 64GB&lt;/td&gt;
&lt;td&gt;io1 256GB 1,000iops&lt;/td&gt;
&lt;td&gt;io1 512GB 2,000iops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost Compute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;85.51&lt;/td&gt;
&lt;td&gt;199.71&lt;/td&gt;
&lt;td&gt;1506.18&lt;/td&gt;
&lt;td&gt;2981.90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost EBS vol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;27.20&lt;/td&gt;
&lt;td&gt;27.20&lt;/td&gt;
&lt;td&gt;450.00&lt;/td&gt;
&lt;td&gt;1286.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost EBS IOPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1560.00&lt;/td&gt;
&lt;td&gt;1820.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Support (Basic)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;351.62&lt;/td&gt;
&lt;td&gt;608.79&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;127.94&lt;/td&gt;
&lt;td&gt;271.34&lt;/td&gt;
&lt;td&gt;3867.80&lt;/td&gt;
&lt;td&gt;6696.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Calculator link&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://calculator.s3.amazonaws.com/index.html#r=IAD&amp;amp;s=EC2&amp;amp;key=files/calc-80c2b4058aa67338d41697a65e2dec0b7dcd6b31&amp;amp;v=ver20200806gR" rel="noopener noreferrer"&gt;Calculator&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://calculator.s3.amazonaws.com/index.html#r=IAD&amp;amp;s=EC2&amp;amp;key=files/calc-5c93e625e2dc193f85de03582f319daebfaf387f&amp;amp;v=ver20200806gR" rel="noopener noreferrer"&gt;Calculator&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://calculator.s3.amazonaws.com/index.html#r=IAD&amp;amp;s=EC2&amp;amp;key=files/calc-4a2db7d6d3a73380df9d4fc02966898bef2845c2&amp;amp;v=ver20200806gR" rel="noopener noreferrer"&gt;Calculator&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://calculator.s3.amazonaws.com/index.html#r=IAD&amp;amp;s=EC2&amp;amp;key=files/calc-b7c5bc1f63c9f58457a022e720e347cb5b80531f&amp;amp;v=ver20200806gR" rel="noopener noreferrer"&gt;Calculator&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*All EC2 instances include additional 20GB gp2 root EBS volume&lt;/p&gt;

&lt;p&gt;AWS cost estimates are based on On-Demand pricing in the North Virginia Region. Cost of snapshots and data transfer are not included. Please consult &lt;a href="https://aws.amazon.com/pricing/" rel="noopener noreferrer"&gt;AWS Pricing&lt;/a&gt;for the latest information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Assets
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deployment Options
&lt;/h3&gt;

&lt;p&gt;The InterSystems IRIS CloudFormation template provides two different deployment options. The multi-AZ deployment option provides a highly available redundant architecture that is suitable for production workloads. The single-AZ deployment option provides a lower cost alternative that is suitable for development or test workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment Assets (Recommended for Production)
&lt;/h3&gt;

&lt;p&gt;The InterSystems IRIS deployment is executed via a CloudFormation template that receives input parameters and passes them to the appropriate nested template. These are executed in order based on conditions and dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Resources Created:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC Security Groups&lt;/li&gt;
&lt;li&gt;EC2 Instances for IRIS nodes and Arbiter&lt;/li&gt;
&lt;li&gt;Amazon Elastic Load Balancing (Amazon ELB) Network Load Balancer (NLB)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CloudFormation Template Input Parameters
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;General AWS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2 Key Name Pair&lt;/li&gt;
&lt;li&gt;EC2 Instance Role&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;S3&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name of S3 bucket where the IRIS distribution file and license key are located&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Network&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The individual VPC and Subnets where resources will be launched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database Master Password&lt;/li&gt;
&lt;li&gt;EC2 instance type for Database nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack Creation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are four outputs for the master template: the JDBC endpoint that can be used to connect JDBC clients to InterSystems IRIS, the public IP of the Bastion Host and private IP addresses for both IRIS nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean Up
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Follow the &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-console-delete-stack.html" rel="noopener noreferrer"&gt;AWS CloudFormation Delete&lt;/a&gt;documentation to delete the resources deployed by this document&lt;/li&gt;
&lt;li&gt;Delete any other resources that you manually created to integrate or assist with the deployment, such as S3 bucket and VPC&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing the Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Health Checks
&lt;/h3&gt;

&lt;p&gt;Follow the template output links to Node 01/02 Management Portal. Login with the username: SuperUser and the password you selected in the CloudFormation template.&lt;/p&gt;

&lt;p&gt;Navigate to System Administration -&amp;amp;gt; Configuration -&amp;amp;gt; Mirror Settings -&amp;amp;gt; Edit Mirror. Make sure the system is configured with two Failover members.&lt;/p&gt;

&lt;p&gt;Verify that the mirrored database is created and active. System Administration -&amp;amp;gt; Configuration -&amp;amp;gt; Local Databases.&lt;/p&gt;

&lt;p&gt;Validate the JDBC connection by following the "First Look JDBC" document: &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_jdbc" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_jdbc&lt;/a&gt; to validate JDBC connectivity to IRIS via the Load Balancer. Make sure to change the url variable to the value displayed in the template output, and password from "SYS" to the one you selected during setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failover Test
&lt;/h3&gt;

&lt;p&gt;On the Node02, navigate to the Management Portal (see "Health Check" section above) and open the Configuration-&amp;amp;gt;Edit Mirror page. At the bottom of the page you will see &lt;em&gt;This member is the backup. Changes must be made on the primary.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Locate the Node01 instance in the AWS EC2 management dashboard. Its name will be of the format: MyStackName-Node01-1NGXXXXXX&lt;/p&gt;

&lt;p&gt;Restart the Node01 instance. This will simulate an instance/AZ outage.&lt;/p&gt;

&lt;p&gt;Reload Node02 "Edit Mirror" page. The status should change to: &lt;em&gt;This member is the primary. Changes will be sent to other members.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Backup and Recovery
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backup
&lt;/h3&gt;

&lt;p&gt;CloudFormation deployment does not enable backups for InterSystems IRIS. We recommend backing up IRIS EBS volumes using EBS Snapshot - &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSSnapshots.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSSnapshots.html&lt;/a&gt; - in combination with IRIS Write Daemon Freeze: &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCDI_backup#GCDI_backup_methods_ext" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCDI_backup#GCDI_backup_methods_ext&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instance Failure
&lt;/h3&gt;

&lt;p&gt;Unhealthy IRIS instances are detected by IRIS mirroring and Load Balancer, and traffic is redirected to another mirror node. Instances that are capable of recovery will rejoin the mirror and continue normal operations. If you encounter persistently unhealthy instances, please see our Knowledge Base and the "Emergency Maintenance" section of this guide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Availability-Zone Failure
&lt;/h3&gt;

&lt;p&gt;In the event of an availability-zone failure, temporary traffic disruptions may occur. Similar to instance failure, IRIS mirroring and Load Balancer would handle the event by switching traffic to the IRIS instance in the remaining available AZ.&lt;/p&gt;

&lt;h3&gt;
  
  
  Region Failure
&lt;/h3&gt;

&lt;p&gt;The architecture outlined in this guide does not deploy a configuration that supports multi-region operation. IRIS asynchronous mirroring and AWS Route53 can be used to build configurations capable of handling region failure with minimal disruption. Please refer to &lt;a href="https://community.intersystems.com/post/intersystems-iris-example-reference-architectures-amazon-web-services-aws" rel="noopener noreferrer"&gt;https://community.intersystems.com/post/intersystems-iris-example-reference-architectures-amazon-web-services-aws&lt;/a&gt; for details.&lt;/p&gt;

&lt;h3&gt;
  
  
  RPO/RTO
&lt;/h3&gt;

&lt;p&gt;Recovery Point Objective (RPO)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single node Dev/Test&lt;/strong&gt; configuration is defined by the time of the last successful backup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi Zone Fault Tolerant&lt;/strong&gt; setup provides Active-Active configuration that ensures full data consistency in the event of failover, with RPO of the last successful transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Recovery Time Objective (RTO)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Backup recovery forthe &lt;strong&gt;Single node Dev/Test&lt;/strong&gt; configuration is outside of the scope of this deployment guide. Please refer to &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-restoring-volume.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-restoring-volume.html&lt;/a&gt; for details on restoring EBS volume snapshots.&lt;/li&gt;
&lt;li&gt;RTO for &lt;strong&gt;Multi Zone Fault Tolerant&lt;/strong&gt; setup is typically defined by the time it takes for the Elastic Load Balancer to redirect traffic to the new Primary Mirror node of the IRIS cluster. You can further reduce RTO time by developing mirror-aware applications or adding an Application Server Connection to the mirror: &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GHA_mirror#GHA_mirror_set_configecp" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GHA_mirror#GHA_mirror_set_configecp&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Storage Capacity
&lt;/h3&gt;

&lt;p&gt;IRIS Journal and Database EBS volumes can reach storage capacity. InterSystems recommends monitoring Journal and Database volume state using the IRIS Dashboard, as well as Linux file-system tools such as df.&lt;/p&gt;

&lt;p&gt;Both Journal and Database volumes can be expanded following the EBS guide &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-modify-volume.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-modify-volume.html&lt;/a&gt;. &lt;strong&gt;Note:&lt;/strong&gt; both EBS volume expansion and Linux file system extension steps need to be performed. Optionally, after a database backup is performed, journal space can be reclaimed by running Purge Journals: &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCDI_journal#GCDI_journal_tasks" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCDI_journal#GCDI_journal_tasks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also consider enabling CloudWatch Agent on your instances to monitor disk space (not enabled by this CloudFormation template): &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security certificate expiration
&lt;/h3&gt;

&lt;p&gt;You can use AWS Certificate Manager (ACM) to easily provision, deploy, manage, and monitor expiration of Secure Sockets Layer/Transport Layer Security (SSL/TLS) certificates.&lt;/p&gt;

&lt;p&gt;Certificates must be monitored for expiration. InterSystems does not provide an integrated process for monitoring certificate expiration. AWS provides a CloudFormation template that can help setup an alarm. Please visit the following link for details: &lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/acm-certificate-expiration-check.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/config/latest/developerguide/acm-certificate-expiration-check.html&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routine Maintenance
&lt;/h2&gt;

&lt;p&gt;For IRIS upgrade procedures in mirrored configurations, please refer to: &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCI_upgrade#GCI_upgrade_tasks_mirrors" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCI_upgrade#GCI_upgrade_tasks_mirrors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;InterSystems recommends following the best practices of AWS and InterSystems for ongoing tasks, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html" rel="noopener noreferrer"&gt;Access key rotation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html" rel="noopener noreferrer"&gt;Service limit evaluation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/acm/latest/userguide/managed-renewal.html" rel="noopener noreferrer"&gt;Certificate renewals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;IRIS License limits and expiration &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_dashboard" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Storage capacity monitoring &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_dashboard" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_dashboard&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, you might consider adding CloudWatch Agent to your EC2 instances: &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emergency Maintenance
&lt;/h2&gt;

&lt;p&gt;If EC2 instances are available, connect to the instance via bastion host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The public IP of the bastion host may change after an instance stop/start. That does not affect availability of the IRIS cluster and JDBC connection.&lt;/p&gt;

&lt;p&gt;For command line access, connect to the IRIS nodes via bastion host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ chmod 400 &amp;lt;my-ec2-key&amp;gt;.pem
$ ssh-add &amp;lt;my-ec2-key&amp;gt;.pem
$ ssh -J ec2-user@&amp;lt;bastion-public-ip&amp;gt; ec2-user@&amp;lt;node-private-ip&amp;gt; -L 52773:&amp;lt;node-private-ip&amp;gt;:52773
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the Management Portal for the instance would be available at: &lt;a href="http://localhost:52773/csp/sys/%25CSP.Portal.Home.zen" rel="noopener noreferrer"&gt;http://localhost:52773/csp/sys/%25CSP.Portal.Home.zen&lt;/a&gt;User: SuperUser, and the password you entered at stack creation.&lt;/p&gt;

&lt;p&gt;To connect to the IRIS command prompt use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ iris session iris
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consult InterSystems IRIS Management and Monitoring guide: &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM" rel="noopener noreferrer"&gt;https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Contact InterSystems Support.&lt;/p&gt;

&lt;p&gt;If EC2 instances are not available/reachable, contact AWS Support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; AZ or instance failures will automatically be handled in our Multi-AZ deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;I cannot "Create stack" in CloudFormation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please check that you have the appropriate permissions to "Create Stack". Contact your AWS account admin for permissions, or AWS Support if you continue to encounter this issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack is being created, but I can't access IRIS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It takes approximately 2 minutes from the moment EC2 instance status turns into "CREATE COMPLETED" to the moment IRIS is fully available. SSH to the EC2 Node instances and check if IRIS is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$iris list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't see any active IRIS instances, or the message "iris: command not found" appears, then IRIS installation has failed. Check &lt;code&gt;$cat /var/log/cloud-init-output.log&lt;/code&gt; on the instance to identify any problems with the IRIS installation during instance first start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IRIS is up, but I can't access either the Management Portal or connect from my [Java] application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make sure that the Security Group created by CloudFormation lists your source IP address as allowed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contact InterSystems Support
&lt;/h3&gt;

&lt;p&gt;InterSystems Worldwide Response Center (WRC) provides expert technical assistance.&lt;/p&gt;

&lt;p&gt;InterSystems IRIS support is always included with your IRIS subscription.&lt;/p&gt;

&lt;p&gt;Phone, email and online support are always available to clients 24 hours a day, 7 days a week. We maintain support advisers in 15 countries around the world and have specialists fluent in English, Spanish, Portuguese, Italian, Welsh, Arabic, Hindi, Chinese, Thai, Swedish, Korean, Japanese, Finnish, Russian, French, German, Hebrew, and Hungarian. Every one of our clients immediately gets help from a highly qualified support specialist who really cares about client success.&lt;/p&gt;

&lt;p&gt;For Immediate Help&lt;/p&gt;

&lt;p&gt;Support phone:&lt;/p&gt;

&lt;p&gt;+1-617-621-0700 (US)&lt;/p&gt;

&lt;p&gt;+44 (0) 844 854 2917 (UK)&lt;/p&gt;

&lt;p&gt;0800615658 (NZ Toll Free)&lt;/p&gt;

&lt;p&gt;1800 628 181 (Aus Toll Free)&lt;/p&gt;

&lt;p&gt;Support email:&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:support@intersystems.com"&gt;support@intersystems.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Support online:&lt;/p&gt;

&lt;p&gt;WRC Direct&lt;/p&gt;

&lt;p&gt;Contact &lt;a href="mailto:support@intersystems.com"&gt;support@intersystems.com&lt;/a&gt; for a login.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  IAM Policy for EC2 instance
&lt;/h3&gt;

&lt;p&gt;The following IAM policy allows the EC2 instance to read objects from the S3 bucket 'my-bucket', and write logs to CloudWatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "S3BucketReadOnly",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::my-bucket/*"
    },
    {
      "Sid": "CloudWatchWriteLogs",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogStreams"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>database</category>
    </item>
  </channel>
</rss>
