{"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-19T20:54:50","modified_gmt":"2026-08-19T18:54:50","slug":"build-an-mcp-server-in-python-beginner-tutorial","status":"publish","type":"post","link":"https:\/\/kindsonthegenius.com\/blog\/build-an-mcp-server-in-python-beginner-tutorial\/","title":{"rendered":"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>Learn how to <strong>build an MCP server in Python<\/strong> on your laptop and plug it into Cursor so the AI can call <em>your<\/em> tools. 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<p><!-- ktg-article-toc --><\/p>\n<nav class=\"ktg-article-toc\" aria-label=\"MCP server Python tutorial table of contents\" style=\"margin:1.5em 0;padding:1em 1.25em;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;\">\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 decoding=\"async\" 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\" loading=\"lazy\" style=\"max-width:100%;height:auto;border-radius:8px;border:1px solid #dee2e6;\" \/><figcaption style=\"font-size:0.85em;color:#666;margin-top:0.5em;\">Host \u2192 MCP \u2192 your Python server \u2192 tools and files<\/figcaption><\/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 decoding=\"async\" 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\" loading=\"lazy\" style=\"max-width:100%;height:auto;border-radius:8px;border:1px solid #dee2e6;\" \/><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><\/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 \/>\nAn 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 \/>\nNo. 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 \/>\nNo. 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 \/>\nAlmost 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 \/>\nLocal 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<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 \/>\n<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 \/>\n<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","protected":false},"excerpt":{"rendered":"<p>Updated August 19, 2026: Beginner guide to building a local MCP server in Python with FastMCP \u2014 includes MCP vs API diagrams, MCP Inspector testing, &hellip; <\/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":2,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2371\/revisions"}],"predecessor-version":[{"id":2373,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2371\/revisions\/2373"}],"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}]}}