<?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: ElzaJose</title>
    <description>The latest articles on DEV Community by ElzaJose (@elzajose).</description>
    <link>https://dev.to/elzajose</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4231512%2F1eed52cf-6f5b-4eba-9e09-c3b0a33bac36.png</url>
      <title>DEV Community: ElzaJose</title>
      <link>https://dev.to/elzajose</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/elzajose"/>
    <language>en</language>
    <item>
      <title>Building Your First Model Context Protocol (MCP) Server: A Team Task Tracker with FastMCP and SQLite</title>
      <dc:creator>ElzaJose</dc:creator>
      <pubDate>Sat, 07 Nov 2026 05:36:42 +0000</pubDate>
      <link>https://dev.to/elzajose/building-your-first-model-context-protocol-mcp-server-a-team-task-tracker-with-fastmcp-and-sqlite-2cbf</link>
      <guid>https://dev.to/elzajose/building-your-first-model-context-protocol-mcp-server-a-team-task-tracker-with-fastmcp-and-sqlite-2cbf</guid>
      <description>&lt;p&gt;Model Context Protocol (MCP) has completely changed how we think about AI integration. Instead of copying and pasting context back and forth, MCP lets LLMs directly call local tools, interact with file systems, and query databases right from your code editor.&lt;/p&gt;

&lt;p&gt;If you are looking to understand how MCP works under the hood by building something practical for your team, building a &lt;strong&gt;Team Task Tracker Assistant&lt;/strong&gt; is one of the best weekend projects.&lt;/p&gt;

&lt;p&gt;Here is how we built a lightweight, local-first MCP server using &lt;strong&gt;Python, FastMCP, SQLite, and GitHub Copilot in VS Code&lt;/strong&gt;, complete with multi-user sync via OneDrive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Build an MCP Task Tracker?
&lt;/h2&gt;

&lt;p&gt;Most team task management tools either lock you into heavy SaaS platforms or require complex cloud deployments. But what if your team just wants to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Paste a raw &lt;strong&gt;Minutes of Meeting (MoM)&lt;/strong&gt; block into chat and have an AI automatically parse, assign, and save action items.&lt;/li&gt;
&lt;li&gt;Filter tasks instantly by assignee, status (e.g., &lt;em&gt;Blocked&lt;/em&gt;, &lt;em&gt;In Progress&lt;/em&gt;), or sprints (&lt;em&gt;Current Sprint&lt;/em&gt;, &lt;em&gt;TBD&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Run everything locally without central cloud overhead, keeping data secure and private.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MCP makes this effortless by turning standard Python functions into native LLM tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: The Foundation with FastMCP
&lt;/h2&gt;

&lt;p&gt;Writing raw JSON-RPC servers for MCP can be tedious. Thankfully, &lt;strong&gt;FastMCP&lt;/strong&gt; abstracts away all the transport setup and schema generation.&lt;/p&gt;

&lt;p&gt;By defining simple Python functions with type hints and docstrings, FastMCP automatically builds the tool schemas that the LLM reads.&lt;/p&gt;

&lt;p&gt;Here is a snippet of how clean tool registration looks using &lt;code&gt;fastmcp&lt;/code&gt;:&lt;br&gt;
&lt;/p&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="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;Team Task Tracker&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&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;assignee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unassigned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Current Sprint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remarks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add a new task with an assignee, sprint timeline, status, and remarks.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Database insertion logic here...
&lt;/span&gt;    &lt;span class="k"&gt;return&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;Task added successfully and assigned to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;assignee&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Scaling from JSON to SQLite for Team Sync
&lt;/h2&gt;

&lt;p&gt;Initially, a local &lt;code&gt;tasks.json&lt;/code&gt; file works great for single-user testing. However, if multiple team members need to access and update tasks, flat JSON files lead to race conditions and file overwrites.&lt;/p&gt;

&lt;p&gt;Switching the backend to &lt;strong&gt;SQLite&lt;/strong&gt; (&lt;code&gt;team_tasks.db&lt;/code&gt;) solves this. SQLite handles concurrent reads and atomic writes efficiently, making it robust enough for small teams.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;

&lt;span class="n"&gt;DB_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;team_tasks.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_db_connection&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;row_factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Row&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init_db&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_db_connection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
        CREATE TABLE IF NOT EXISTS tasks (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            assignee TEXT DEFAULT &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Unassigned&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,
            due_date TEXT DEFAULT &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Current Sprint&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,
            status TEXT DEFAULT &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pending&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,
            remarks TEXT DEFAULT &lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="s"&gt;
        )
    &lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Wiring It Up to VS Code &amp;amp; GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;To let GitHub Copilot in VS Code talk to your server, you register it via an &lt;code&gt;mcp.json&lt;/code&gt; configuration file. This tells VS Code to spin up your Python script in the background over standard input/output (&lt;code&gt;stdio&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;"servers"&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;"team-todo-tracker"&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;"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;"stdio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"/path/to/your/team_tracker_server.py"&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;
  
  
  Making it Work Across the Team (OneDrive Sync)
&lt;/h3&gt;

&lt;p&gt;To share this across your team without deploying a cloud server, place &lt;code&gt;team_tracker_server.py&lt;/code&gt; and your SQLite database inside a &lt;strong&gt;shared OneDrive folder&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When configured on each team member's VS Code instance, everyone shares the exact same task database and interacts with it using natural language through Copilot Agent mode.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Putting It into Action
&lt;/h2&gt;

&lt;p&gt;Once configured, your workflow changes completely. You can open Copilot Chat in Agent mode and give commands like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parsing MoMs:&lt;/strong&gt;
&amp;gt; &lt;em&gt;"Here are the notes from today's sync: Rahul is fixing the login bottleneck for next sprint, and Ananya is blocked on UI styling because we are waiting for brand guidelines. Update the tracker."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Behind the scenes, Copilot breaks this into sequential tool calls (&lt;code&gt;add_task&lt;/code&gt; with remarks and statuses) and executes them instantly.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filtering &amp;amp; Tracking:&lt;/strong&gt;
&amp;gt; &lt;em&gt;"Show all tasks assigned to Rahul that are currently blocked."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Copilot calls &lt;code&gt;list_tasks(assignee="Rahul", status="Blocked")&lt;/code&gt; and renders a clean summary right in your chat window.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building this project is one of the fastest ways to grasp the core pillars of MCP: &lt;strong&gt;tool schemas, persistent state, and multi-step tool execution loops&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whether you want to build a personal assistant, a DevOps tracker, or a team workflow manager, wrapping standard Python functions with FastMCP opens up endless possibilities for local automation. Give it a try on your next setup!&lt;/p&gt;

&lt;p&gt;Get the complete source code and setup guide on GitHub &lt;a href="https://github.com/ElzaJose/MCPEnabled_ToDos" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>python</category>
      <category>sqlite</category>
    </item>
  </channel>
</rss>
