Expondo Dados Legíveis como Recursos MCP
Um recurso é como um servidor MCP entrega dados para um cliente ler, endereçado por um URI em vez de invocado como uma função.
Busque em todas as páginas da documentação
Um recurso é como um servidor MCP entrega dados para um cliente ler, endereçado por um URI em vez de invocado como uma função.
Arquivos, linhas de banco de dados, valores de configuração e respostas de API são todos adequados para recursos, uma vez que você os pensa como coisas que um cliente pode buscar, não ações que um modelo executa.
Recursos no SDK Python do MCP são definidos com o decorador @mcp.resource(), recebendo um URI ou um template de URI como argumento.
Um recurso estático tem um URI fixo, como config://settings, enquanto um recurso com template usa placeholders entre chaves, como notes://{name}, que se tornam parâmetros de função.
Como um recurso representa uma leitura, ele nunca deve mutar estado ou disparar um efeito colateral; essa distinção é o que separa um recurso de uma ferramenta.
Retornar um tipo MIME sensato junto com o conteúdo ajuda o cliente a decidir como renderizar ou analisar o que recebe de volta, seja texto puro, JSON ou uma imagem.
Recursos combinam naturalmente com ferramentas: uma ferramenta pode criar ou atualizar algo, e um recurso pode expor o resultado para leitura posterior.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("resource-recipes")
@mcp.resource("config://settings")
def get_settings() -> str:
"""Retorna a configuração atual do servidor como JSON."""
return '{"theme": "dark", "max_results": 20}'
@mcp.resource("notes://{name}")
def get_note(name: str) -> str:
"""Retorna o conteúdo de uma nota nomeada."""
return f"Contents of note '{name}'"Quando usar isso:
# server.py
import json
import sqlite3
from pathlib import Path
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("docs-resources")
DB_PATH = Path(__file__).parent / "articles.db"
DOCS_DIR = Path(__file__).parent / "docs"
def _get_db() -> sqlite3.Connection:
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
@mcp.resource("articles://{article_id}")
def get_article(article_id: str) -> str:
"""Retorna uma única linha de artigo como JSON, endereçada por seu id."""
conn = _get_db()
try:
row = conn.execute(
"SELECT id, title, body FROM articles WHERE id = ?", (article_id,)
).fetchone()
if row is None:
raise ValueError(f"No article found with id {article_id}")
return json.dumps(dict(row))
finally:
conn.close()
@mcp.resource("docs://{filename}")
def get_doc_file(filename: str) -> str:
"""Retorna o texto bruto de um arquivo markdown do diretório docs."""
path = (DOCS_DIR / filename).resolve()
if DOCS_DIR.resolve() not in path.parents:
raise ValueError("filename must stay within the docs directory")
if not path.exists():
raise FileNotFoundError(f"No such file: {filename}")
return path.read_text(encoding="utf-8")
@mcp.resource("stats://article-count")
def get_article_count() -> str:
"""Retorna o número total de artigos como um recurso de resumo estático."""
conn = _get_db()
try:
count = conn.execute("SELECT COUNT(*) FROM articles").fetchone()[0]
return json.dumps({"article_count": count})
finally:
conn.close()
if __name__ == "__main__":
mcp.run(transport="stdio")O que isso demonstra:
articles://{article_id}) lendo uma única linha de uma conexão de banco de dados real.docs://{filename}) servindo arquivos do disco, com uma proteção contra travessia de caminho antes de tocar no sistema de arquivos.stats://article-count) sem parâmetros, útil para resumos ou dados agregados.json.dumps para que dados estruturados retornem em um formato previsível e analisável.@mcp.resource("scheme://path/{param}") registra um template de URI; o SDK extrai segmentos {param} e os passa como argumentos de função quando um URI correspondente é lido.list_resources() (para URIs concretos) ou list_resource_templates() (para os com template), então lê um URI específico com read_resource(uri).config://, notes://, docs://) é arbitrária e escolhida por você; não é um protocolo de rede real, apenas um namespace.| Estilo de Esquema | Exemplo | Melhor Para |
|---|---|---|
| Namespace customizado | config://settings | Conceitos específicos do servidor sem equivalente em sistema de arquivos ou rede |
Estilo file:// | file:///reports/q1.csv | Dados que genuinamente mapeiam para um caminho de arquivo real |
| Estilo de Domínio | articles://{id}, users://{id} | Linhas ou registros de um banco de dados, um esquema por tipo de entidade |
| Agregado/Estático | stats://summary | Resumos ou contagens fixas, sem parâmetros |
# Protege contra travessia de caminho sempre que um recurso lê do disco usando um
# segmento de caminho fornecido pelo usuário.
path = (DOCS_DIR / filename).resolve()
if DOCS_DIR.resolve() not in path.parents:
raise ValueError("filename must stay within the docs directory")| Elemento | Tipo | Descrição |
|---|---|---|
| Template de URI | str | Passado para @mcp.resource(); segmentos {param} se tornam parâmetros de função |
| Parâmetros de função | str (tipicamente) | Extraídos dos segmentos de URI correspondentes no momento da leitura |
| Valor de retorno | str (ou bytes para conteúdo binário) | O conteúdo do recurso, retornado ao cliente em read_resource |
send_email://{to} implica um efeito colateral e engana clientes que assumem que recursos são seguros para ler repetidamente. Correção: mova qualquer coisa com efeito colateral para uma ferramenta.docs://../../etc/passwd pode escapar do diretório pretendido se você construir um caminho ingenuamente. Correção: resolva o caminho e confirme que ele permanece dentro da raiz permitida antes de ler.json.dumps(...) para qualquer coisa com mais de um campo.try/finally (como mostrado acima) ou um gerenciador de contexto de conexão.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Ferramenta MCP | A operação executa uma ação ou computação com um resultado | Os dados são uma leitura simples sem efeitos colaterais |
| Prompt MCP | Você está empacotando instruções, não dados | O cliente precisa buscar ou referenciar conteúdo real |
| Uma ferramenta que retorna os mesmos dados | Você quer que o modelo solicite explicitamente os dados como parte de uma etapa de raciocínio | Você quer que o cliente anexe ou cache os dados como contexto ambiente |
| Acesso direto ao banco de dados/API fora do MCP | Apenas uma aplicação interna precisará desses dados | Você quer os mesmos dados endereçáveis do Claude Code, Claude Desktop e outros clientes |
Sim, um template como orgs://{org_id}/users/{user_id} extrai ambos os segmentos como parâmetros de função separados, desde que cada um seja delimitado por seu próprio segmento de caminho.
Não. Recursos destinam-se a ser lidos com segurança, potencialmente mais de uma vez, sem alterar nada. Qualquer coisa que crie, atualize ou exclua dados pertence a uma ferramenta.
Retorne bytes do manipulador em vez de str, e o SDK o codificará apropriadamente para o cliente, tipicamente junto com um tipo MIME explícito descrevendo o formato binário.
O servidor retorna um erro indicando que o recurso não existe. É por isso que o esquema de URI exato e o design do template importam, já que um erro de digitação no esquema significa que nenhum manipulador corresponde.
Sim. list_resources() e list_resource_templates() permitem que um cliente descubra o que um servidor oferece, incluindo recursos com template e seus placeholders {param} mostrados, antes de ler qualquer URI específico.
Não, é apenas um namespace que você escolhe. MCP não exige que estes sejam URIs de rede resolvíveis; eles só precisam ser únicos e significativos dentro do seu servidor.
Levante uma exceção com uma mensagem específica, exatamente como faria em um manipulador de ferramenta, em vez de retornar uma string vazia ou None que esconde a falha.
Sim, recursos podem ser tão dinâmicos quanto qualquer função manipuladora; cada leitura é executada de forma fresca, então um recurso baseado em banco de dados sempre reflete os dados atuais, a menos que você adicione sua própria camada de cache.
Não. Sempre resolva o caminho resultante e verifique se ele permanece dentro do diretório raiz pretendido antes de ler, para evitar travessia de caminho para arquivos fora do diretório servido.
application/json é a escolha convencional, e retornar conteúdo via json.dumps(...) junto com esse tipo MIME dá aos clientes uma carga útil previsível e analisável.
Sim, um único servidor FastMCP pode registrar qualquer número de recursos estáticos (config://settings) e com template (articles://{id}) lado a lado.
Versões da Stack: Escrito contra a linha de modelos Claude atual em ~junho de 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (o padrão), e Claude Haiku 4.5 - e a especificação atual do Model Context Protocol. Nomes de modelos, versões de SDK e a especificação MCP mudam rapidamente - verifique os detalhes atuais em platform.claude.com/docs e modelcontextprotocol.io antes de confiar neles.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026