{"id":2371,"date":"2026-08-19T20:37:51","date_gmt":"2026-08-19T18:37:51","guid":{"rendered":"https:\/\/kindsonthegenius.com\/blog\/build-an-mcp-server-in-python-beginner-tutorial\/"},"modified":"2026-08-27T16:55:57","modified_gmt":"2026-08-27T14:55:57","slug":"build-an-mcp-server-in-python","status":"publish","type":"post","link":"https:\/\/kindsonthegenius.com\/blog\/build-an-mcp-server-in-python\/","title":{"rendered":"How to Build an MCP Server in Python (Beginner Tutorial)"},"content":{"rendered":"<p><!-- ktg-updated-banner --><\/p>\n<div class=\"ktg-updated-banner\" style=\"margin: 1em 0; padding: 0.75em 1em; background: #eff6ff; border-left: 4px solid #3b82f6; border-radius: 4px;\">\n<p><strong>Updated August 19, 2026:<\/strong> Beginner guide to building a local <strong>MCP server in Python<\/strong> with FastMCP \u2014 includes MCP vs API diagrams, MCP Inspector testing, and Cursor setup. Companion to the Alkademy AI Lab video.<\/p>\n<\/div>\n<p><em>Learn how to <strong>build an MCP server in Python<\/strong> on your laptop and plug it into Cursor so the AI can call your tools. <\/em><\/p>\n<p>This beginner <strong>mcp server python<\/strong> tutorial uses FastMCP, a local notes example (no API keys), and clear diagrams that show how MCP relates to a normal API.<\/p>\n<p><strong>Prerequisites:<\/strong> Python 3.10+, basic Python functions, and Cursor installed. Helpful background: <a href=\"https:\/\/kindsonthegenius.com\/blog\/introduction-to-machine-learning-ml\/\">Introduction to Machine Learning<\/a> and the <a href=\"https:\/\/kindsonthegenius.com\/python\/\">Python tutorials<\/a> hub.<\/p>\n<p><strong>Estimated time:<\/strong> 30\u201345 minutes. <strong>Difficulty:<\/strong> Beginner.<\/p>\n<h2>TL;DR<\/h2>\n<ul>\n<li>\n<p><strong>MCP (Model Context Protocol)<\/strong> provides a standard way for AI applications like Cursor to interact with tools and data.<\/p>\n<\/li>\n<li>\n<p><strong>FastMCP<\/strong> makes it easy to turn regular Python functions into MCP tools that an AI host can call.<\/p>\n<\/li>\n<li>\n<p>In this tutorial, you\u2019ll build a local <strong>notes MCP server<\/strong> with <code>add_note<\/code>, <code>list_notes<\/code>, and <code>search_notes<\/code> tools.<\/p>\n<\/li>\n<li>\n<p>The server stores notes in a simple <code>notes.json<\/code> file, so you don\u2019t need a database or API keys.<\/p>\n<\/li>\n<li>\n<p>You\u2019ll use <strong>MCP Inspector<\/strong> to test the server before connecting it to Cursor.<\/p>\n<\/li>\n<li>\n<p>You\u2019ll configure <strong>Cursor<\/strong> to launch your Python server and call its tools through MCP.<\/p>\n<\/li>\n<li>\n<p>The same approach can later be extended to APIs, databases, files, business systems, and other AI-powered workflows.<\/p>\n<\/li>\n<\/ul>\n<p><!-- ktg-article-toc --><\/p>\n<nav class=\"ktg-article-toc\" style=\"margin: 1.5em 0; padding: 1em 1.25em; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px;\" aria-label=\"MCP server Python tutorial table of contents\">\n<h2>Table of Contents<\/h2>\n<ol>\n<li><a href=\"#what-is-mcp\">What is MCP?<\/a><\/li>\n<li><a href=\"#mcp-vs-api\">MCP vs an API<\/a><\/li>\n<li><a href=\"#what-you-build\">What you will build<\/a><\/li>\n<li><a href=\"#setup\">Project setup<\/a><\/li>\n<li><a href=\"#write-server\">Write the FastMCP server<\/a><\/li>\n<li><a href=\"#inspector\">Test with MCP Inspector<\/a><\/li>\n<li><a href=\"#cursor\">Connect to Cursor<\/a><\/li>\n<li><a href=\"#use-tools\">Use your tools live<\/a><\/li>\n<li><a href=\"#next-steps\">Next steps<\/a><\/li>\n<li><a href=\"#faq\">Frequently asked questions<\/a><\/li>\n<\/ol>\n<\/nav>\n<h2 id=\"what-is-mcp\">What Is MCP?<\/h2>\n<p><strong>MCP<\/strong> stands for <strong>Model Context Protocol<\/strong>. In plain English: it is a standard plug so AI apps can talk to tools and data.<\/p>\n<p>Think of it as <strong>USB for AI tools<\/strong>:<\/p>\n<ul>\n<li><strong>Host<\/strong> \u2014 Cursor or Claude Desktop (the AI app)<\/li>\n<li><strong>MCP<\/strong> \u2014 the standard protocol between host and server<\/li>\n<li><strong>Your server<\/strong> \u2014 a Python process you write<\/li>\n<li><strong>Tools<\/strong> \u2014 normal functions the model can call (save a note, search a file, call an API)<\/li>\n<\/ul>\n<figure style=\"margin: 1.5em 0; text-align: center;\"><img loading=\"lazy\" decoding=\"async\" style=\"max-width: 100%; height: auto; border-radius: 8px; border: 1px solid #dee2e6;\" src=\"https:\/\/kindsonthegenius.com\/blog\/wp-content\/uploads\/2026\/08\/mcp-host-server-tools-1.png\" alt=\"MCP architecture diagram showing Host, MCP protocol, Python server, and tools\" width=\"800\" height=\"360\" \/>\n<figcaption style=\"font-size: 0.85em; color: #666; margin-top: 0.5em;\">Host \u2192 MCP \u2192 your Python server \u2192 tools and files<\/figcaption>\n<\/figure>\n<p>Without MCP, you copy context into a chat window and paste answers back. With MCP, the model <strong>calls your function<\/strong>, gets a real result, and keeps going.<\/p>\n<h2 id=\"mcp-vs-api\">MCP vs an API<\/h2>\n<p>Beginners often ask: isn\u2019t MCP just an API? They are related \u2014 but the <strong>client<\/strong> is different.<\/p>\n<figure style=\"margin: 1.5em 0; text-align: center;\"><img loading=\"lazy\" decoding=\"async\" style=\"max-width: 100%; height: auto; border-radius: 8px; border: 1px solid #dee2e6;\" src=\"https:\/\/kindsonthegenius.com\/blog\/wp-content\/uploads\/2026\/08\/mcp-vs-api-diagram-1.png\" alt=\"Side-by-side diagram: classic API where your app calls HTTP endpoints versus MCP where Cursor calls your Python tools\" width=\"1000\" height=\"525\" \/>\n<figcaption style=\"font-size: 0.85em; color: #666; margin-top: 0.5em;\">Left: you call the API. Right: the AI host calls your MCP tools.<\/figcaption>\n<\/figure>\n<ul>\n<li><strong>Same idea<\/strong> \u2014 both expose capabilities over a standard interface.<\/li>\n<li><strong>Different client<\/strong> \u2014 with an API, <em>you<\/em> (or your app) decide when to call <code>GET \/notes<\/code>. With MCP, the <em>model<\/em> chooses <code>add_note<\/code>.<\/li>\n<li><strong>Can combine<\/strong> \u2014 an MCP tool can call an API under the hood. MCP does not replace APIs; it sits in front of the AI so the model can use your capabilities as tools.<\/li>\n<\/ul>\n<p><strong>One-liner:<\/strong> APIs are for programs you write. MCP is for AI apps that need to use your programs as tools.<\/p>\n<h2 id=\"what-you-build\">What You Will Build<\/h2>\n<p>A <strong>personal notes MCP<\/strong> with three tools:<\/p>\n<ol>\n<li><code>add_note<\/code> \u2014 save a title and body<\/li>\n<li><code>list_notes<\/code> \u2014 list titles<\/li>\n<li><code>search_notes<\/code> \u2014 find notes by keyword<\/li>\n<\/ol>\n<p>Data lives in a local <code>notes.json<\/code> file. No database, no cloud API keys.<\/p>\n<h2 id=\"setup\">Step 1: Project Setup<\/h2>\n<p>Confirm Python 3.10+:<\/p>\n<pre><code class=\"language-bash\">python3 --version<\/code><\/pre>\n<p>Create the project with <a href=\"https:\/\/docs.astral.sh\/uv\/\" target=\"_blank\" rel=\"noopener noreferrer\">uv<\/a> (preferred by the official MCP docs). Pip also works.<\/p>\n<pre><code class=\"language-bash\">mkdir notes-mcp &amp;&amp; cd notes-mcp\nuv init\nuv add \"mcp[cli]\"<\/code><\/pre>\n<p>The <code>[cli]<\/code> extra installs the <code>mcp<\/code> command \u2014 especially <code>mcp dev<\/code>, which opens the <strong>MCP Inspector<\/strong> so you can test tools before wiring Cursor.<\/p>\n<h2 id=\"write-server\">Step 2: Write the FastMCP Server<\/h2>\n<p>Create <code>server.py<\/code>. FastMCP (from the official <code>mcp<\/code> package) turns normal Python functions into tools. Type hints become the argument schema; the <strong>docstring<\/strong> becomes the description the model sees.<\/p>\n<pre><code class=\"language-python\">import json\nfrom pathlib import Path\n\nfrom mcp.server.fastmcp import FastMCP\n\nmcp = FastMCP(\"notes\")\nNOTES_FILE = Path(__file__).with_name(\"notes.json\")\n\n\ndef _load() -&gt; list[dict]:\n    if not NOTES_FILE.exists():\n        return []\n    return json.loads(NOTES_FILE.read_text())\n\n\ndef _save(notes: list[dict]) -&gt; None:\n    NOTES_FILE.write_text(json.dumps(notes, indent=2))\n\n\n@mcp.tool()\ndef add_note(title: str, body: str) -&gt; str:\n    \"\"\"Save a new note with a title and body.\"\"\"\n    notes = _load()\n    notes.append({\"title\": title, \"body\": body})\n    _save(notes)\n    return f\"Saved note: {title}\"\n\n\n@mcp.tool()\ndef list_notes() -&gt; str:\n    \"\"\"List all saved note titles.\"\"\"\n    notes = _load()\n    if not notes:\n        return \"No notes yet.\"\n    return \"\\n\".join(f\"- {n['title']}\" for n in notes)\n\n\n@mcp.tool()\ndef search_notes(query: str) -&gt; str:\n    \"\"\"Search notes by keyword in title or body.\"\"\"\n    q = query.lower()\n    hits = [\n        n for n in _load()\n        if q in n[\"title\"].lower() or q in n[\"body\"].lower()\n    ]\n    if not hits:\n        return f\"No notes matching '{query}'.\"\n    return \"\\n\\n\".join(f\"**{n['title']}**\\n{n['body']}\" for n in hits)\n\n\nif __name__ == \"__main__\":\n    mcp.run()\n<\/code><\/pre>\n<p><strong>Important:<\/strong> default transport is <strong>stdio<\/strong> \u2014 Cursor starts this process and talks over stdin\/stdout. Never use <code>print()<\/code> for debugging in stdio mode; it corrupts the protocol stream. Use logging or write to stderr instead.<\/p>\n<h2 id=\"inspector\">Step 3: Test with MCP Inspector<\/h2>\n<p>Prove the server works <em>before<\/em> you blame Cursor:<\/p>\n<pre><code class=\"language-bash\">uv run mcp dev server.py<\/code><\/pre>\n<p>The Inspector opens in your browser. You should see <code>add_note<\/code>, <code>list_notes<\/code>, and <code>search_notes<\/code>. Call each tool once. If Inspector works, your Python is fine \u2014 later connection issues are usually config paths.<\/p>\n<h2 id=\"cursor\">Step 4: Connect to Cursor<\/h2>\n<p>Open MCP settings in Cursor:<\/p>\n<ul>\n<li><strong>Command Palette<\/strong> (<kbd>Cmd\/Ctrl+Shift+P<\/kbd>) \u2192 search <strong>MCP<\/strong> \u2192 <strong>View: Open MCP Settings<\/strong>, or<\/li>\n<li><strong>Cursor Settings<\/strong> (<kbd>Cmd\/Ctrl+,<\/kbd>) \u2192 <strong>Tools &amp; MCP<\/strong><\/li>\n<\/ul>\n<p>Add a server (or edit <code>.cursor\/mcp.json<\/code> \/ <code>~\/.cursor\/mcp.json<\/code>) with an <strong>absolute path<\/strong> to your project:<\/p>\n<pre><code class=\"language-json\">{\n  \"mcpServers\": {\n    \"notes\": {\n      \"command\": \"uv\",\n      \"args\": [\n        \"run\",\n        \"--directory\",\n        \"\/ABSOLUTE\/PATH\/TO\/notes-mcp\",\n        \"python\",\n        \"server.py\"\n      ]\n    }\n  }\n}<\/code><\/pre>\n<p>Refresh or toggle the server until <code>notes<\/code> shows connected. Cursor launches your process as a subprocess over stdio.<\/p>\n<h2 id=\"use-tools\">Step 5: Use Your Tools Live<\/h2>\n<p>In Cursor chat, try:<\/p>\n<pre><code>Add a note titled \"MCP tip\" with body \"Docstrings become the tool description the model sees.\"<\/code><\/pre>\n<pre><code>List my notes.<\/code><\/pre>\n<pre><code>Search my notes for docstring.<\/code><\/pre>\n<p>Watch the tool call in the UI \u2014 Cursor ran <em>your<\/em> function. Optionally open <code>notes.json<\/code> to confirm the file changed on disk.<\/p>\n<h2 id=\"next-steps\">Next Steps<\/h2>\n<ul>\n<li>Add an MCP <strong>resource<\/strong> (read-only data) or <strong>prompt<\/strong> template.<\/li>\n<li>Point the same server at Claude Desktop via its config file \u2014 same idea, different host.<\/li>\n<li>For remote \/ shared servers later: Streamable HTTP instead of stdio.<\/li>\n<li>Go deeper on AI foundations: <a href=\"https:\/\/kindsonthegenius.com\/blog\/introduction-to-machine-learning-ml\/\">Introduction to Machine Learning<\/a><\/li>\n<li>Build grounded chatbots: <a href=\"https:\/\/kindsonthegenius.com\/blog\/how-to-build-a-rag-chatbot-with-spring-boot-and-openai-step-by-step\/\">Spring Boot RAG chatbot tutorial<\/a><\/li>\n<li>Practice Python: <a href=\"https:\/\/kindsonthegenius.com\/python\/\">Python tutorials<\/a><\/li>\n<\/ul>\n<h2 id=\"faq\">Frequently Asked Questions<\/h2>\n<p><strong>What is an MCP server in Python?<\/strong><br \/>An MCP server is a Python process that exposes tools (and optionally resources\/prompts) to AI hosts like Cursor using the Model Context Protocol. With FastMCP, you decorate normal functions and the host can call them.<\/p>\n<p><strong>Is MCP the same as an API?<\/strong><br \/>No. Both expose capabilities, but an API is typically called by code you write, while MCP is designed for AI hosts. An MCP tool can wrap an API under the hood.<\/p>\n<p><strong>Do I need Claude Desktop to use MCP?<\/strong><br \/>No. Cursor supports MCP. Claude Desktop is another common host. The same local stdio server idea applies to both.<\/p>\n<p><strong>Why does my MCP server fail in Cursor but work in the Inspector?<\/strong><br \/>Almost always config: wrong absolute path, wrong command\/args, or the server not refreshed after editing <code>mcp.json<\/code>. If Inspector works, trust your Python and fix the host config.<\/p>\n<p><strong>Why can\u2019t I use print() in an MCP server?<\/strong><br \/>Local servers often use stdio. Printing to stdout mixes with the protocol messages and can break the connection. Use logging or stderr for debug output.<\/p>\n<h2>Final Thoughts<\/h2>\n<p>Building an MCP server in Python is a practical way to understand how AI applications can interact with your own tools, data, and workflows. With FastMCP, you can turn familiar Python functions into tools that AI hosts such as Cursor can discover and use.<\/p>\n<p>The notes example in this tutorial is intentionally simple, but the same pattern can be extended to much more useful applications. You could connect an MCP server to internal APIs, databases, files, business systems, or other services and give AI applications controlled access to those capabilities.<\/p>\n<p>The important first step is understanding the architecture: the AI host decides when it needs a tool, MCP provides the communication standard, and your server provides the functionality. Once that makes sense, you can move from a local experiment to building MCP servers for real-world AI workflows.<\/p>\n<p><!-- ktg-alkademy-cta --><\/p>\n<div class=\"ktg-alkademy-cta\" style=\"margin: 2em 0; padding: 1.25em; border-left: 4px solid #2563eb; background: #f8fafc;\">\n<p><strong>Prefer learning by video?<\/strong> This post accompanies the Alkademy AI Lab tutorial on building an MCP server in Python. For instructor-led AI and software courses, visit <a href=\"https:\/\/www.alkademy.com\/courses\" target=\"_blank\" rel=\"noopener noreferrer\">Alkademy<\/a>.<\/p>\n<\/div>\n<p><!-- ktg-faq-schema --><br \/><script type=\"application\/ld+json\">{\"@context\":\"https:\/\/schema.org\",\"@type\":\"FAQPage\",\"mainEntity\":[{\"@type\":\"Question\",\"name\":\"What is an MCP server in Python?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"An MCP server is a Python process that exposes tools to AI hosts like Cursor using the Model Context Protocol. With FastMCP you decorate normal functions and the host can call them.\"}},{\"@type\":\"Question\",\"name\":\"Is MCP the same as an API?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"No. Both expose capabilities, but an API is typically called by code you write while MCP is designed for AI hosts. An MCP tool can wrap an API under the hood.\"}},{\"@type\":\"Question\",\"name\":\"Do I need Claude Desktop to use MCP?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"No. Cursor supports MCP and Claude Desktop is another common host. The same local stdio server idea applies to both.\"}},{\"@type\":\"Question\",\"name\":\"Why does my MCP server fail in Cursor but work in the Inspector?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Usually a config issue: wrong absolute path, wrong command or args, or the server not refreshed after editing mcp.json. If the Inspector works, fix the host config.\"}},{\"@type\":\"Question\",\"name\":\"Why can\u2019t I use print() in an MCP server?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Local MCP servers often use stdio. Printing to stdout can corrupt the protocol stream. Use logging or stderr instead.\"}}]}<\/script><\/p>\n<p><!-- ktg-howto-schema --><br \/><script type=\"application\/ld+json\">{\"@context\":\"https:\/\/schema.org\",\"@type\":\"HowTo\",\"name\":\"Build an MCP Server in Python\",\"description\":\"Beginner guide to create a local MCP server with FastMCP, test it in the MCP Inspector, and connect it to Cursor.\",\"totalTime\":\"PT45M\",\"tool\":[{\"@type\":\"HowToTool\",\"name\":\"Python 3.10+\"},{\"@type\":\"HowToTool\",\"name\":\"uv or pip\"},{\"@type\":\"HowToTool\",\"name\":\"Cursor\"}],\"step\":[{\"@type\":\"HowToStep\",\"name\":\"Create the project\",\"text\":\"Create a notes-mcp folder and install mcp[cli] with uv or pip.\"},{\"@type\":\"HowToStep\",\"name\":\"Write the FastMCP server\",\"text\":\"Create server.py with add_note, list_notes, and search_notes tools using @mcp.tool().\"},{\"@type\":\"HowToStep\",\"name\":\"Test with MCP Inspector\",\"text\":\"Run uv run mcp dev server.py and call each tool in the browser UI.\"},{\"@type\":\"HowToStep\",\"name\":\"Connect to Cursor\",\"text\":\"Add the server in Cursor MCP settings or mcp.json with an absolute project path.\"},{\"@type\":\"HowToStep\",\"name\":\"Use the tools\",\"text\":\"Ask Cursor to add, list, and search notes and confirm the tool calls succeed.\"}]}<\/script><\/p>\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to build an MCP server in Python on your laptop and plug it into Cursor so the AI can call your tools.<\/p>\n","protected":false},"author":1,"featured_media":2374,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pagelayer_contact_templates":[],"_pagelayer_content":"","footnotes":""},"categories":[16],"tags":[],"class_list":["post-2371","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-machine-learning"],"acf":[],"_links":{"self":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2371","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/comments?post=2371"}],"version-history":[{"count":4,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2371\/revisions"}],"predecessor-version":[{"id":2506,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2371\/revisions\/2506"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/media\/2374"}],"wp:attachment":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/media?parent=2371"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/categories?post=2371"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/tags?post=2371"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}