Publicado em 18 de Set
(Versão em Espanhol) Construindo Ferramentas MCP: Um Servidor de Processamento de PDF
Model Context Protocol (MCP) emergiu como um padrão revolucionário para conectar modelos de IA com ferramentas e serviços externos para melhorar suas capacidades. Vou guiá-lo através de uma visão geral de alto nível do processo de desenvolvimento para construir um servidor integral de processamento de PDF usando FastMCP, com arquitetura adequada, tratamento de erros e recursos de produção.
Ferramentas Disponíveis de um Vistazo
Servidor e Utilidades de Arquivos
server_info(): Obter a configuração e estado do servidor.list_temp_resources(): Listar arquivos atualmente no diretório temporário do servidor.upload_file(),upload_file_base64(),upload_file_url(): Enviar arquivos para o servidor a partir da sua máquina local ou de uma URL.get_resource_base64(): Baixar um arquivo do diretório temporário do servidor.
Texto e Metadados
get_pdf_info(): Obter rapidamente a contagem de páginas, tamanho do arquivo e estado de criptografia.extract_text(): Extrair o conteúdo de texto completo de um PDF.extract_text_by_page(): Extrair texto de páginas específicas ou intervalos de páginas.extract_metadata(): Ler os metadados do PDF (autor, título, data de criação, etc.).
Manipulação de PDF
merge_pdfs(): Combinar vários arquivos PDF em um único documento.split_pdf(): Dividir um PDF em vários arquivos menores com base em intervalos de páginas.rotate_pages(): Rotacionar páginas específicas dentro de um PDF.
Conversão
pdf_to_images(): Converter páginas específicas do PDF em arquivos de imagem (PNG, JPEG).images_to_pdf(): Criar um novo PDF a partir de uma lista de arquivos de imagem.
Você pode encontrar o código base no Repositório do GitHub
Servidor MCP PDF
Nosso Caso de Estudo: Rastreando a Ferramenta “extract_text”
Vamos explorar ‘extract_text’; todas as outras ferramentas compartilham um fluxo de trabalho consistente e são facilmente acessíveis no repositório, se você quiser revisá-lo.
Padrão
Ao separar a lógica em “Serviço” → “Ferramenta” → “Registro”, mantemos o código limpo, testável e fácil de estender. Você pode adicionar sua própria ferramenta seguindo exatamente este padrão.
Passo 1: A Lógica Central - o “Serviço”
Antes de pensar em servidores, ferramentas ou protocolos, precisamos de uma função simples e confiável em Python que possa realizar nossa tarefa central. Esta é então a “Camada de Serviço”, o motor.
Arquivo: src/fastmcp_pdf_server/services/pdf_processor.py
Nosso primeiro passo é escrever uma função que receba um caminho de arquivo e retorne o texto. Usamos a biblioteca “pdfplumber” para isso. Observe que a função retorna uma classe de dados “TextExtractionResult”, que ajuda a garantir uma estrutura de dados consistente.
from __future__ import annotations
from dataclasses import dataclass
from typing import List
import pdfplumber
from ..utils.validators import validate_pdf
# Uma classe de dados fornece um tipo de retorno estruturado e previsível para nosso serviço.
# É como uma classe leve e auto-documentada.
@dataclass
class TextExtractionResult:
text: str
page_count: int
char_count: int
def extract_text(file_path: str, encoding: str = "utf-8") -> TextExtractionResult:
# Primeiro, executar o arquivo através de um validador para garantir que existe, é um PDF,
# e está dentro dos limites de tamanho permitidos. Isso falha cedo se a entrada for ruim.
pdf_path = validate_pdf(file_path)
# Usar pdfplumber para abrir e processar robustamente o PDF.
with pdfplumber.open(str(pdf_path)) as pdf:
texts: List[str] = []
for page in pdf.pages:
# Extrair texto, padrão uma string vazia se uma página não tiver texto.
texts.append(page.extract_text() or "")
# Unir o texto de todas as páginas em uma única string.
text = "\n".join(texts)
# Retornar uma instância de nossa classe de dados, garantindo que o contrato seja cumprido.
return TextExtractionResult(text=text, page_count=len(texts), char_count=len(text))
```Esta função é Python puro. Não sabe nada sobre FastMCP. Pode ser testada unitariamente com "pytest" ou usada em uma aplicação completamente diferente. Esta separação é a base de um sistema mantível. Uma vez que fizemos nossa lógica de serviço, continuamos com a "Ferramenta" MCP.
## Passo 2: A Ponte - A "Ferramenta"
Agora precisamos expor nossa função de serviço para o mundo exterior como uma Ferramenta MCP. Esta "Camada de Ferramenta" age como uma ponte. Lida com a realidade desordenada de uma chamada de ferramenta e a traduz em uma chamada limpa para nosso serviço.
**Arquivo:** `src/fastmcp_pdf_server/tools/text_extraction.py`
Esta é a peça mais crítica do quebra-cabeça. Lidará com a chamada da ferramenta, resolverá o arquivo, chamará o serviço e formatará a resposta.
Dentro de src/fastmcp_pdf_server/tools/text_extraction.py
from future import annotations
import time
import uuid
from typing import Any
from fastmcp import FastMCP # type: ignore
from ..services import pdf_processor
from ..services.file_manager import resolve_to_path
from ..utils.logger import get_logger
logger = get_logger(name)
A função ‘register’ é uma convenção para agrupar registros de ferramentas.
A aplicação principal chamará esta função, passando a si mesma como argumento.
def register(app: FastMCP) → None:
# O decorador @app.tool() é o que oficialmente registra esta função como uma ferramenta MCP.
@app.tool()
async def extract_text(file: Any, encoding: str | None = “utf-8”) → dict:
“”"Extrair todo o texto de um PDF.
Aceita:
- String de caminho completo
- Nome de arquivo curto previamente escrito no armazenamento temporário
- Bytes / tipo arquivo / dict com base64 (será salvo no temporário)
"""
# 1. Gerar um ID único para esta operação específica. Isso é crucial para
# rastrear um único pedido através dos logs.
op_id = uuid.uuid4().hex
start = time.perf_counter()
try:
# 2. Resolver a entrada flexível 'file' (que poderia ser um caminho, nome de arquivo, ou
# objeto base64) em um caminho de arquivo absoluto concreto e validado.
resolved = resolve_to_path(file, filename_hint="uploaded.pdf")
# 3. Chamar a função de serviço limpa e testável com o caminho resolvido.
# Aqui é onde acontece o processamento real do PDF.
res = pdf_processor.extract_text(str(resolved), encoding or "utf-8")
# 4. O serviço retorna uma classe de dados. Agora formatamos isso no
# dicionário final amigável para JSON para o cliente.
duration_ms = int((time.perf_counter() - start) * 1000)
return {
"text": res.text,
"page_count": res.page_count,
"char_count": res.char_count,
# O bloco 'meta' fornece dados operacionais valiosos ao cliente.
"meta": {
"operation_id": op_id,
"execution_ms": duration_ms,
"resolved_path": str(resolved),
},
}
except Exception as e: # noqa: BLE001
# 5. Esta é a rede de segurança. Se qualquer parte do processo falhar,
# registrar o erro completo para depuração...
logger.error("extract_text error: %s", e)
hint = (
"Forneça um caminho completo, faça upload do arquivo primeiro via 'upload_file', "
"ou passe bytes/base64. Exemplo de payload:\n"
"{\n"
" \"name\": \"upload_file\",\n"
" \"arguments\": {\n"
" \"file\": { \"base64\": \"<...>\", \"filename\": \"my.pdf\" }\n"
" }\n"
"}"
)
# ...e lançar um ValueError simples. FastMCP converterá isso em uma
# resposta de erro limpa e estruturada para o LLM, prevenindo um crash.
raise ValueError(f"extract_text failed: {e}. {hint}")
A ferramenta é apenas um wrapper. É um gerente que coordena outras partes do código. Lida com entradas desordenadas, chama a lógica de serviço limpa e embala a resposta final. O padrão 'try...except ValueError' é uma melhor prática crítica.
## Passo 3: A Conexão Final - O "Registro"
Nossa função de ferramenta está definida, mas a aplicação do servidor ainda não sabe que existe. O passo final é conectar, ou registrar, nosso módulo de ferramenta com a instância principal da aplicação "FastMCP".
**Arquivo:** `src/fastmcp_pdf_server/main.py`
Este arquivo é o ponto de entrada de todo nosso servidor. Seu trabalho é construir o objeto aplicação e registrar todos os conjuntos de ferramentas.
Dentro de src/fastmcp_pdf_server/main.py
from future import annotations
from typing import Any
from .config import settings
from .utils.logger import get_logger
logger = get_logger(name)
def build_app() → Any:
# Este bloco try/except fornece um erro amigável se o usuário
# esqueceu de instalar as dependências de requirements.txt.
try:
from fastmcp import FastMCP # type: ignore
except Exception as exc: # pragma: no cover
raise SystemExit(
“fastmcp não está instalado. Por favor instale as dependências primeiro.”
) from exc
# Inicializar a aplicação principal, obtendo nome e versão de config.
app = FastMCP(settings.server_name, version=settings.server_version)
# --- Registro de Ferramentas ---
# Importar os módulos que contêm nossas definições de ferramentas.
from .tools import utilities, text_extraction, pdf_manipulation, conversion, uploads
from .services.file_manager import cleanup_expired
# Chamar a função 'register' de cada módulo para anexar suas ferramentas à app.
# Esta abordagem modular mantém o arquivo principal limpo.
utilities.register(app)
text_extraction.register(app)
pdf_manipulation.register(app)
conversion.register(app)
uploads.register(app)
# --- Tarefas de Início ---
# É uma boa prática executar tarefas de limpeza no início.
# Aqui, removemos qualquer arquivo antigo do diretório temporário.
try:
cleanup_expired()
except Exception as exc: # noqa: BLE001
logger.error("cleanup_expired no início falhou: %s", exc)
return app
Ao importar módulos e chamar uma função "register" de cada um. O arquivo principal se mantém limpo e age como um resumo de alto nível das capacidades do servidor. Adicionar ou remover toda uma categoria de ferramentas é tão simples quanto adicionar ou remover uma linha aqui.
## O Panorama Completo
Agora, rastreemos um pedido de princípio a fim:
1. Um LLM chama a ferramenta `extract_text`.
2. A aplicação `FastMCP`, construída em `main.py`, roteia a chamada para a função async `extract_text` dentro de `text_tools.py`.
3. A função da ferramenta chama `resolve_to_path` para obter um caminho de arquivo limpo.
4. A função da ferramenta então chama o serviço `pdf_processor.extract_text` com esse caminho limpo.
5. O serviço faz o trabalho pesado e retorna um dicionário simples: `{'text': ..., 'page_count': ...}`.
6. A função da ferramenta recebe este dicionário, adiciona o `char_count` e o bloco `meta`, e retorna o dicionário final enriquecido.
7. `FastMCP` envia este dicionário final de volta ao LLM como uma resposta JSON.
## O Resultado Final
Usando Claude Desktop como Cliente MCP podemos testar nossa ferramenta "extract_text" de nosso servidor, simplesmente registrando o MCP, adicionando-o ao arquivo de configuração "claude_desktop_config.json"
{
“mcpServers”: {
“pdf-processor-server”: {
“command”: “D:\Github Projects\mcp_pdf_server\.venv\Scripts\python.exe”,
“args”: [
“-m”,
“fastmcp_pdf_server”
],
“env”: {
“TEMP_DIR”: “D:\Github Projects\mcp_pdf_server\temp_files”
}
}
}
}
[](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%2Fdiesxdd65n7z43bc4xyy.png)
Normalmente, para este tipo de Clientes MCP, você deve adicionar ao seu prompt o uso do Servidor MCP, neste caso, nosso "PDF Processor Server"; às vezes, também é necessário especificar o caminho completo do arquivo.
[](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%2Fzei96rk0aw55mcbkfde5.png)
## Para Onde Ir a Partir Daqui?
Você conseguiu! Você configurou um servidor, aprendeu como se conectar a ele, ordenou que extraísse texto e até deu uma olhada sob o capô para ver como tudo funciona.
O que vem a seguir?
* **Explore Outras Ferramentas**: Dê uma olhada no arquivo `README.md`. Você encontrará uma lista completa de outras ferramentas que você pode chamar, como `merge_pdfs`, `split_pdf` e `pdf_to_images`.
* **Estenda o Servidor**: Tente adicionar sua própria ferramenta! Siga o padrão.
* **Automatize sua vida**: Pense em seus próprios fluxos de trabalho. Você poderia usar este servidor para extrair automaticamente texto de faturas? Ou para combinar seus relatórios semanais em um único PDF? O poder é seu.
Happy Coding! 🤖
