{"id":5556,"date":"2026-02-06T17:37:05","date_gmt":"2026-02-06T20:37:05","guid":{"rendered":"https:\/\/rbcc.net.br\/wp\/construindo-seu-mcp-com-fastmcp\/"},"modified":"2026-02-06T17:37:05","modified_gmt":"2026-02-06T20:37:05","slug":"construindo-seu-mcp-com-fastmcp","status":"publish","type":"post","link":"https:\/\/rbcc.net.br\/wp\/construindo-seu-mcp-com-fastmcp\/","title":{"rendered":"Construindo seu MCP com FastMCP"},"content":{"rendered":"<p><\/p>\n<div>\n<p><a href=\"https:\/\/dev.to\/t\/mcp\">#mcp<\/a><a href=\"https:\/\/dev.to\/t\/python\"> <\/a><a href=\"https:\/\/dev.to\/t\/python\">#python<\/a><a href=\"https:\/\/dev.to\/t\/claude\"> <\/a><a href=\"https:\/\/dev.to\/t\/claude\">#claude<\/a><a href=\"https:\/\/dev.to\/t\/docker\"> <\/a><a href=\"https:\/\/dev.to\/t\/docker\">#docker<\/a><\/p>\n<p><strong>INTRODU\u00c7\u00c3O:<\/strong><\/p>\n<p>Se voc\u00ea usa intelig\u00eancia artificial, sabe que ela tem um limite claro: ela \u00e9 muito inteligente, mas vive presa dentro de uma caixa de texto. O Claude, por exemplo, pode escrever um poema sobre chuva, mas n\u00e3o sabe se est\u00e1 chovendo agora na minha cidade. Ele pode simular uma venda, mas n\u00e3o consegue dar baixa no meu estoque real. E por que estou dizendo isso, por que neste artigo eu vou te ensinar a criar ferramentas MCP com conex\u00f5es API de clima e conex\u00e3o com um banco de dados SQLite.<\/p>\n<p>Decidi resolver isso explorando o MCP (Model Context Protocol). A ideia era simples: dar \u201cm\u00e3os\u201d para a IA interagir com meus dados locais e APIs externas. O resultado foi um reposit\u00f3rio que foi desde um script Python b\u00e1sico at\u00e9 uma aplica\u00e7\u00e3o dockerizada completa.<\/p>\n<p>Meu primeiro desafio foi arquitetural. Eu precisava de duas capacidades distintas:<\/p>\n<ul class=\"wp-block-list\">\n<li>Acessar um banco de dados SQLite local (uma opera\u00e7\u00e3o s\u00edncrona).<\/li>\n<li>Consultar uma API de clima na internet (uma opera\u00e7\u00e3o ass\u00edncrona).<\/li>\n<\/ul>\n<p>Em vez de criar v\u00e1rios microsservi\u00e7os complexos, unifiquei tudo em um \u00fanico arquivo que chamei de \u201csuper_server.py\u201d. Utilizando a biblioteca FastMCP, consegui misturar fun\u00e7\u00f5es normais com fun\u00e7\u00f5es \u201casync\u201d no mesmo agente. Isso permitiu que o Claude, em uma \u00fanica resposta, verificasse que estava chovendo em Londres e, baseado nisso, sugerisse vender guarda-chuvas do meu banco de dados local.<\/p>\n<p>Ficando desta forma o c\u00f3digo (Ao final vou disponibilizar o link no meu github):<\/p>\n<p><code>import sqlite3<br \/>import os<br \/>import httpx<br \/>from mcp.server.fastmcp import FastMCP<br \/>from dotenv import load_dotenv<br \/>from typing import Annotated<br \/>from pydantic import Field<\/code><\/p>\n<p><code>load_dotenv()<\/code><\/p>\n<p><code>server_name = os.getenv(\"MCP_SERVER_NAME\", \"Assistente Padr\u00e3o\")<br \/>mcp = FastMCP(server_name)<\/code><\/p>\n<p><code>BASE_DIR = os.path.dirname(os.path.abspath(<strong>file<\/strong>))<br \/>db_name = os.getenv(\"DB_FILENAME\", \"loja.db\")<br \/>DB_PATH = os.path.join(BASE_DIR, db_name)<\/code><\/p>\n<p><code>GEO_URL = os.getenv(\"GEO_API_URL\")<br \/>WEATHER_URL = os.getenv(\"WEATHER_API_URL\")<\/code><\/p>\n<pre class=\"wp-block-code\"><code>    if not items:\n        return \"Nenhum produto encontrado.\"\n\n    resultado = \"ID | Produto | Pre\u00e7o (R$) | Estoque\\n\"\n    resultado += \"-\" * 40 + \"\\n\"\n    for item in items:\n        resultado += f\"{item[0]} | {item[1]} | {item[2]:.2f} | {item[3]}\\n\"\n    return resultado\nexcept Exception as e:\n    return f\"Erro ao acessar banco de dados: {str(e)}\"\n@mcp.tool()\ndef listar_produtos() -&gt; str:\n\"\"\"Lista todos os produtos do estoque com pre\u00e7os e quantidades.\"\"\"\ntry:\nwith sqlite3.connect(DB_PATH) as conn:\ncursor = conn.cursor()\ncursor.execute(\"SELECT id, nome, preco, estoque FROM produtos\")\nitems = cursor.fetchall()\n\n@mcp.tool()\ndef vender_produto(\nnome_exato: str,\nquantidade: Annotated[int, Field(description=\"Quantidade vendida.\")]\n) -&gt; str:\n\"\"\"Registra uma venda e abate do estoque no banco de dados.\"\"\"<\/code><\/pre>\n<pre class=\"wp-block-code\"><code># Valida\u00e7\u00e3o Manual (Soft Fail)\nif quantidade &lt;= 0:\n    return \"Erro: A quantidade para venda deve ser maior que zero. Por favor, tente novamente com um valor positivo.\"\n\ntry:\n    with sqlite3.connect(DB_PATH) as conn:\n        cursor = conn.cursor()\n        cursor.execute(\"SELECT estoque FROM produtos WHERE nome = ?\", (nome_exato,))\n        res = cursor.fetchone()\n\n        if not res:\n            return f\"Erro: Produto '{nome_exato}' n\u00e3o encontrado.\"\n\n        estoque_atual = res[0]\n        if estoque_atual &lt; quantidade:\n            return f\"Estoque insuficiente. Restam apenas {estoque_atual}.\"\n\n        novo_estoque = estoque_atual - quantidade\n        cursor.execute(\"UPDATE produtos SET estoque = ? WHERE nome = ?\", (novo_estoque, nome_exato))\n        conn.commit()\n\n    return f\"Venda realizada! Saldo de '{nome_exato}': {novo_estoque}.\"\nexcept Exception as e:\n    return f\"Erro ao processar venda: {str(e)}\"<\/code><\/pre>\n<h2 class=\"wp-block-heading\">\u2014 BLOCO 2: FERRAMENTAS DE CLIMA \u2014<\/h2>\n<pre class=\"wp-block-code\"><code><a href=\"https:\/\/dev.to\/mcp\">@mcp<\/a>.tool()\nasync def obter_previsao(cidade: str) -&gt; str:\n\"\"\"Consulta API externa para ver o clima atual (Async).\"\"\"\nasync with httpx.AsyncClient() as client:\ntry:\n# Busca Lat\/Lon\nresp_geo = await client.get(GEO_URL, params={\"name\": cidade, \"count\": 1, \"language\": \"pt\"})\nresp_geo.raise_for_status()\ndata_geo = resp_geo.json()\n\n if \"results\" not in data_geo:\n            return f\"Cidade '{cidade}' n\u00e3o encontrada.\"\n\n        local = data_geo[\"results\"][0]\n\n        # Busca Clima\n        params_clima = {\n            \"latitude\": local[\"latitude\"],\n            \"longitude\": local[\"longitude\"],\n            \"current\": [\"temperature_2m\", \"relative_humidity_2m\"],\n            \"timezone\": \"auto\"\n        }\n        resp_weather = await client.get(WEATHER_URL, params=params_clima)\n        data_weather = resp_weather.json()\n        curr = data_weather[\"current\"]\n\n        return (f\"Clima em {local['name']}: {curr['temperature_2m']}\u00b0C, \"\n                f\"Umidade: {curr['relative_humidity_2m']}%\")\n\n    except Exception as e:\n        return f\"Erro na conex\u00e3o: {str(e)}\"<\/code><\/pre>\n<h2 class=\"wp-block-heading\">\u2014 BLOCO 3: PROMPTS \u2014<\/h2>\n<p><a href=\"https:\/\/dev.to\/mcp\">@mcp<\/a>.prompt()<br \/>def assistente_vendas() -&gt; str:<br \/>\u201c\u201d\u201dPrompt pronto para atuar como vendedor proativo.\u201d\u201d\u201d<br \/>return \u201c\u201d\u201d<br \/>Voc\u00ea \u00e9 um assistente de vendas inteligente.<br \/>Sua miss\u00e3o \u00e9:<br \/>1. Verificar o clima da cidade do usu\u00e1rio.<br \/>2. Sugerir produtos do estoque que combinem com o clima.<br \/>Use as ferramentas dispon\u00edveis para consultar os dados reais.<br \/>\u201c\u201d\u201d<\/p>\n<p>if\u00a0<strong>name<\/strong>\u00a0== \u201c<strong>main<\/strong>\u201c:<br \/>mcp.run()`<\/p>\n<p>Para que seja possivel utilizar esse custom MCP no seu Claude Desktop voc\u00ea deve alterar as configura\u00e7\u00f5es no claude_desktop_config.json que geralmente fica no diret\u00f3rio Roaming\/claude.<\/p>\n<p class=\"has-text-align-left\"><code>{<br \/>\"mcpServers\": {<br \/>\"super-servidor-docker\": {<br \/>\"command\": \"wsl.exe\",<br \/>\"args\": [<br \/>\"docker\",<br \/>\"run\",<br \/>\"-i\",<br \/>\"--rm\",<br \/>\"--env-file\",<br \/>\"\/home\/airtonlirajr\/Estudos\/mcp_learning\/.env\",<br \/>\"mcp-super-server\"<br \/>]<br \/>}<br \/>},<br \/>\"preferences\": {<br \/>\"coworkScheduledTasksEnabled\": false,<br \/>\"sidebarMode\": \"chat\"<br \/>}<br \/>}<\/code><\/p>\n<p>Desta forma basta salvar e abrir novamente o Claude Desktop e veja o resultado que massa:<\/p>\n<figure class=\"wp-block-image size-full\"><figcaption class=\"wp-element-caption\">image<\/figcaption><\/figure>\n<p>OBS: Sim sou de JAMPA \u2013 Jo\u00e3o Pessoa<\/p>\n<p>Ap\u00f3s tudo estar funcionando resolvi dockerizar o projeto com o seguinte Dockerfile:<\/p>\n<p>`# 1. Usa uma imagem oficial do Python, leve (slim)<br \/>FROM python:3.11-slim<\/p>\n<p><code>ENV PYTHONUNBUFFERED=1<\/code><\/p>\n<p><code>ENV PYTHONDONTWRITEBYTECODE=1<\/code><\/p>\n<p><code>WORKDIR \/app<\/code><\/p>\n<p><code>COPY requirements.txt .<br \/>RUN pip install --no-cache-dir -r requirements.txt<\/code><\/p>\n<p><code>COPY . .<\/code><\/p>\n<p><code>RUN python criar_banco.py<\/code><\/p>\n<pre class=\"wp-block-code\"><code>\nCMD [\"python\", \"super_server.py\"]`\n\nTamb\u00e9m criei um script python que esta mencionado no Dockefile para alimentar meu SQLite com dados fict\u00edcios:\n\n`import sqlite3\n\ndef setup_database():\n# Cria o arquivo 'loja.db'\nconn = sqlite3.connect(\"loja.db\")\ncursor = conn.cursor()\n\n# Cria tabela de Produtos\ncursor.execute(\"\"\"\n    CREATE TABLE IF NOT EXISTS produtos (\n        id INTEGER PRIMARY KEY,\n        nome TEXT NOT NULL,\n        preco REAL NOT NULL,\n        estoque INTEGER NOT NULL\n    )\n\"\"\")\n\n# Insere dados de exemplo (se a tabela estiver vazia)\ncursor.execute(\"SELECT count(*) FROM produtos\")\nif cursor.fetchone()[0] == 0:\n    dados = [\n        (\"Notebook Gamer\", 4500.00, 10),\n        (\"Mouse Sem Fio\", 120.50, 50),\n        (\"Monitor 4K\", 1800.00, 15),\n        (\"Teclado Mec\u00e2nico\", 350.00, 30),\n        (\"Cadeira Ergon\u00f4mica\", 850.00, 5)\n    ]\n    cursor.executemany(\"INSERT INTO produtos (nome, preco, estoque) VALUES (?, ?, ?)\", dados)\n    conn.commit()\n    print(\"Banco de dados 'loja.db' criado com sucesso!\")\nelse:\n    print(\"Banco de dados j\u00e1 existe.\")\n\nconn.close()\n\nif\u00a0<strong>name<\/strong>\u00a0== \"<strong>main<\/strong>\":\nsetup_database()`\n<\/code><\/pre>\n<ul class=\"wp-block-list\">\n<li>Montagem de a imagem Docker e execu\u00e7\u00e3o do server MCP no docker: Vamos mergulhar nos detalhes. No mundo do Docker, existem dois momentos principais: Construir (Build) e Rodar (Run).<\/li>\n<\/ul>\n<p>\u00c9 como cozinhar: primeiro voc\u00ea prepara o prato (Build) e depois voc\u00ea serve o prato (Run). O Claude s\u00f3 consegue \u201ccomer\u201d o prato se voc\u00ea souber servir corretamente.<\/p>\n<p>Aqui est\u00e1 a anatomia completa dos comandos que usamos:<\/p>\n<ol class=\"wp-block-list\">\n<li>O Comando de Constru\u00e7\u00e3o (docker build)<\/li>\n<\/ol>\n<pre class=\"wp-block-code\"><code>docker build -t mcp-super-server .<\/code><\/pre>\n<p>Este comando pega o seu Dockerfile (a receita) e o seu c\u00f3digo Python e os funde em um arquivo est\u00e1tico e imut\u00e1vel chamado\u00a0<strong>Imagem.<\/strong><\/p>\n<ul class=\"wp-block-list\">\n<li>docker build: O comando base que diz \u201cquero criar uma nova imagem\u201d.<\/li>\n<li>-t mcp-super-server: O \u201ct\u201d vem de Tag (etiqueta). Sem isso, sua imagem teria um nome aleat\u00f3rio tipo a1b2c3d4. Aqui estamos batizando ela de mcp-super-server para ficar f\u00e1cil de chamar depois.<\/li>\n<li>. (O Ponto Final): Muito importante. Esse ponto diz ao Docker: \u201cUse os arquivos da pasta onde estou agora como contexto\u201d. \u00c9 aqui que ele acha o Dockerfile, o requirements.txt e o super_server.py<\/li>\n<\/ul>\n<ol class=\"wp-block-list\">\n<li>O Comando de Execu\u00e7\u00e3o (docker run) Este \u00e9 o comando que o Claude executa. Ele pega a imagem (que est\u00e1 parada no disco) e cria um Container (um processo vivo na mem\u00f3ria).<\/li>\n<\/ol>\n<pre class=\"wp-block-code\"><code>docker run -i --rm --env-file .env mcp-super-server<\/code><\/pre>\n<p>Cada \u201cflag\u201d (op\u00e7\u00e3o com tra\u00e7o) aqui foi escolhida cirurgicamente para o funcionamento do MCP<\/p>\n<p><strong>CONCLUS\u00c3O<\/strong><\/p>\n<p>Basicamente, o que fizemos aqui foi dar um corpo f\u00edsico para o c\u00e9rebro da IA. At\u00e9 ontem, o Claude era apenas um consultor inteligente preso numa janela de chat, sonhando com o mundo l\u00e1 fora. Hoje, com o Docker e o MCP, voc\u00ea deu a ele permiss\u00e3o para tocar nesse mundo.<\/p>\n<p>Agora que voc\u00ea tem essa estrutura rodando, o \u201cbrinquedo\u201d virou uma ferramenta poderosa. Pense no que d\u00e1 para fazer apenas trocando as ferramentas que criamos:<\/p>\n<p>Leve para a Nuvem: Como seu agente j\u00e1 est\u00e1 num container, voc\u00ea pode hosped\u00e1-lo em servi\u00e7os como Render ou Railway. Isso transformaria seu c\u00f3digo local em um servidor online 24 horas. Imagine poder puxar o celular na rua, falar com o Claude e ele consultar seu banco de dados que est\u00e1 rodando seguro na nuvem.<\/p>\n<p>Automa\u00e7\u00e3o da Vida Real: E se, em vez de consultar estoque, voc\u00ea criasse uma ferramenta para controlar as luzes da sua casa? O Claude poderia cruzar a informa\u00e7\u00e3o de \u201chora do p\u00f4r do sol\u201d da API de clima e acender a luz do seu escrit\u00f3rio automaticamente.<\/p>\n<p>O Assistente Financeiro Definitivo: Voc\u00ea poderia substituir o banco de dados da loja pelo seu banco de dados financeiro pessoal. Imagine mandar a foto de uma nota fiscal para o chat, e o agente n\u00e3o apenas ler o valor, mas inserir o gasto na categoria correta do seu banco de dados SQL, verificar se voc\u00ea estourou o or\u00e7amento do m\u00eas e te dar um pux\u00e3o de orelha, tudo em segundos.<\/p>\n<p>Voc\u00ea deixou de ser apenas um usu\u00e1rio que digita prompts para se tornar um arquiteto de sistemas inteligentes. A barreira t\u00e9cnica foi quebrada. O c\u00f3digo est\u00e1 a\u00ed, modular, seguro e pronto. Agora \u00e9 s\u00f3 escolher qual problema chato do seu dia a dia voc\u00ea quer que a IA resolva para voc\u00ea.<\/p>\n<p>Bebam agua, e me seguem no LinkedIn:\u00a0<a href=\"https:\/\/www.linkedin.com\/in\/airton-de-souza-lira-junior-6b81a661\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.linkedin.com\/in\/airton-de-souza-lira-junior-6b81a661\/<\/a><\/p>\n<p>Reposit\u00f3rio do Projeto:\u00a0<a href=\"https:\/\/github.com\/AirtonLira\/mcp_learning\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/AirtonLira\/mcp_learning<\/a><\/p>\n<\/p><\/div>\n<p>fonte <a href=\"https:\/\/santotech.com.br\/construindo-seu-mcp-com-fastmcp\/\">https:\/\/santotech.com.br\/construindo-seu-mcp-com-fastmcp\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>#mcp #python #claude #docker INTRODU\u00c7\u00c3O: Se voc\u00ea usa intelig\u00eancia artificial, sabe que ela tem um limite claro: ela \u00e9 muito inteligente, mas vive presa dentro de uma caixa de texto. O Claude, por exemplo, pode escrever um poema sobre chuva, mas n\u00e3o sabe se est\u00e1 chovendo agora na minha cidade. Ele pode simular uma venda, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":5557,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8342],"tags":[],"estados-cidades":[],"tags-noticias":[],"class_list":["post-5556","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-not_economia_criativa"],"_links":{"self":[{"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/posts\/5556","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/comments?post=5556"}],"version-history":[{"count":0,"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/posts\/5556\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/media\/5557"}],"wp:attachment":[{"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/media?parent=5556"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/categories?post=5556"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/tags?post=5556"},{"taxonomy":"estados-cidades","embeddable":true,"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/estados-cidades?post=5556"},{"taxonomy":"tags-noticias","embeddable":true,"href":"https:\/\/rbcc.net.br\/wp\/wp-json\/wp\/v2\/tags-noticias?post=5556"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}