Skip to main content
Glama

Personal MCP Server

by pablicio
CONTRIBUTING.md10.5 kB
# 🏗️ Guia de Desenvolvimento ## Arquitetura do Projeto ### Estrutura de Diretórios ``` mcp-tools2/ ├── config/ # Configurações centralizadas │ ├── settings.py # Settings com Pydantic │ └── logging.py # Configuração de logs │ ├── core/ # Núcleo do servidor MCP │ ├── server.py # Servidor principal │ ├── registry.py # Registro de ferramentas │ └── security.py # Validações de segurança │ ├── modules/ # Módulos funcionais (plugins) │ ├── base.py # Classe base para todos os módulos │ ├── filesystem/ # Módulo de sistema de arquivos │ ├── tasks/ # Módulo de tarefas │ └── calendar/ # Módulo Google Calendar │ ├── utils/ # Utilitários compartilhados │ └── tests/ # Testes centralizados ├── unit/ # Testes unitários ├── integration/ # Testes de integração └── quick_test.py # Teste rápido de validação ``` ## Criar um Novo Módulo ### 1. Estrutura Básica Crie um novo diretório em `modules/`: ``` modules/ └── meu_modulo/ ├── __init__.py ├── tools.py # Ferramentas do módulo └── README.md # Documentação do módulo ``` ### 2. Implementar a Classe ```python # modules/meu_modulo/tools.py from modules.base import BaseModule from typing import Dict, Callable, Any class MeuModuloTools(BaseModule): """Descrição do seu módulo.""" def __init__(self): super().__init__() # Inicialize atributos específicos aqui self.meu_recurso = None async def is_available(self) -> bool: """ Verifica se o módulo está disponível. Retorne False se depende de recursos externos não disponíveis. """ # Exemplo: verificar se uma API está acessível return True async def initialize(self): """ Inicializa o módulo. Configure recursos, carregue configurações, etc. """ try: # Inicialização aqui self.meu_recurso = "configurado" self.initialized = True self.logger.info("Módulo inicializado com sucesso") except Exception as e: self.logger.error(f"Erro na inicialização: {e}") raise def get_tools(self) -> Dict[str, Callable]: """ Retorna dicionário com as ferramentas disponíveis. Chave: nome da ferramenta Valor: função/método """ return { "minha_ferramenta": self.minha_ferramenta, "outra_ferramenta": self.outra_ferramenta, } async def minha_ferramenta(self, param: str) -> str: """ Descrição da ferramenta. Esta descrição será vista pelo Claude. Args: param: Descrição do parâmetro Returns: Descrição do retorno """ try: self.logger.info(f"Executando minha_ferramenta com {param}") # Lógica da ferramenta aqui resultado = f"Processado: {param}" return resultado except Exception as e: self.logger.error(f"Erro em minha_ferramenta: {e}") return f"Erro: {e}" async def outra_ferramenta(self, **kwargs) -> Dict[str, Any]: """Outra ferramenta do módulo.""" # Implementação return {"status": "ok"} async def cleanup(self): """ Limpa recursos ao desligar o módulo. """ await super().cleanup() # Limpeza específica do módulo self.meu_recurso = None ``` ### 3. Registrar no Servidor Edite `core/server.py`: ```python from modules.meu_modulo.tools import MeuModuloTools class MCPPersonalServer: # ... async def load_modules(self): """Carrega todos os módulos disponíveis.""" modules_to_load = [ ("calendar", CalendarTools), ("filesystem", FilesystemTools), ("tasks", TasksTools), ("meu_modulo", MeuModuloTools), # Adicione aqui ] # ... resto do código ``` ### 4. Criar Testes ```python # tests/unit/test_meu_modulo.py import pytest from modules.meu_modulo.tools import MeuModuloTools @pytest.mark.asyncio async def test_is_available(): """Testa se o módulo está disponível.""" module = MeuModuloTools() assert await module.is_available() == True @pytest.mark.asyncio async def test_initialize(): """Testa inicialização do módulo.""" module = MeuModuloTools() await module.initialize() assert module.initialized == True @pytest.mark.asyncio async def test_minha_ferramenta(): """Testa a ferramenta principal.""" module = MeuModuloTools() await module.initialize() resultado = await module.minha_ferramenta("teste") assert "Processado" in resultado ``` ## Convenções de Código ### Nomenclatura - **Classes**: PascalCase (`MinhaClasse`) - **Funções/Métodos**: snake_case (`minha_funcao`) - **Constantes**: UPPER_SNAKE_CASE (`MINHA_CONSTANTE`) - **Privados**: Prefixo underscore (`_metodo_privado`) ### Documentação Use docstrings para todas as funções públicas: ```python async def minha_funcao(param1: str, param2: int = 0) -> Dict[str, Any]: """ Breve descrição de uma linha. Descrição mais detalhada se necessário. Explique o que a função faz, não como ela faz. Args: param1: Descrição do primeiro parâmetro param2: Descrição do segundo parâmetro (opcional) Returns: Dicionário com os resultados contendo: - key1: Descrição - key2: Descrição Raises: ValueError: Quando param1 é inválido RuntimeError: Quando algo dá errado Example: >>> resultado = await minha_funcao("teste", 10) >>> print(resultado["key1"]) valor """ pass ``` ### Logging Use logging apropriadamente: ```python self.logger.debug("Informação de debug detalhada") self.logger.info("Operação normal importante") self.logger.warning("Algo inesperado mas não crítico") self.logger.error("Erro que precisa atenção") self.logger.critical("Erro crítico do sistema") ``` ### Tratamento de Erros Sempre trate exceções adequadamente: ```python async def operacao_com_erro(self): """Exemplo de tratamento de erros.""" try: # Operação que pode falhar resultado = await operacao_perigosa() return resultado except ValueError as e: # Erro esperado e tratável self.logger.warning(f"Valor inválido: {e}") return {"error": str(e)} except Exception as e: # Erro inesperado self.logger.error(f"Erro inesperado: {e}", exc_info=True) raise ``` ## Testes ### Executar Testes ```bash # Todos os testes pytest tests/ -v # Testes específicos pytest tests/unit/test_meu_modulo.py -v # Com coverage pytest tests/ --cov=. --cov-report=html # Teste rápido python -m tests.quick_test ``` ### Escrever Testes - Um arquivo de teste por módulo - Use fixtures para setup/teardown - Teste casos normais e de erro - Use mocks para dependências externas ```python import pytest from unittest.mock import Mock, patch @pytest.fixture async def meu_modulo(): """Fixture que cria instância do módulo.""" module = MeuModuloTools() await module.initialize() yield module await module.cleanup() @pytest.mark.asyncio async def test_com_fixture(meu_modulo): """Teste usando a fixture.""" resultado = await meu_modulo.minha_ferramenta("teste") assert resultado is not None ``` ## Segurança ### Validação de Entrada Sempre valide entrada do usuário: ```python from pathlib import Path from core.security import SecurityValidator async def operacao_com_arquivo(self, filepath: str) -> str: """Exemplo de validação segura.""" # Validar caminho validator = SecurityValidator() if not validator.is_path_allowed(filepath): raise ValueError(f"Acesso negado: {filepath}") # Sanitizar entrada safe_path = Path(filepath).resolve() # Continuar operação... ``` ### Limites Implemente limites razoáveis: ```python MAX_RESULTS = 1000 TIMEOUT_SECONDS = 30 async def operacao_com_limite(self): """Exemplo com timeout.""" try: resultado = await asyncio.wait_for( operacao_demorada(), timeout=TIMEOUT_SECONDS ) return resultado except asyncio.TimeoutError: self.logger.error("Operação excedeu timeout") raise ``` ## Performance ### Operações Assíncronas Use async/await para operações I/O: ```python import aiofiles import asyncio async def ler_multiplos_arquivos(self, arquivos: List[str]): """Lê múltiplos arquivos em paralelo.""" async def ler_arquivo(filepath): async with aiofiles.open(filepath, 'r') as f: return await f.read() # Executa em paralelo resultados = await asyncio.gather( *[ler_arquivo(f) for f in arquivos] ) return resultados ``` ### Cache Use cache quando apropriado: ```python from functools import lru_cache @lru_cache(maxsize=128) def operacao_cara(param: str) -> str: """Cacheia resultados de operações custosas.""" # Operação cara aqui return resultado ``` ## Debugging ### Modo Debug Ative debug no `.env`: ```env DEBUG=true LOG_LEVEL=DEBUG ``` ### Python Debugger Use pdb para debugging: ```python import pdb async def funcao_com_bug(self): # ... código ... pdb.set_trace() # Breakpoint aqui # ... mais código ... ``` ### Logs Úteis Adicione logs úteis para debugging: ```python self.logger.debug(f"Estado atual: {self.estado}") self.logger.debug(f"Parâmetros recebidos: {locals()}") ``` ## Versionamento - Use semantic versioning (MAJOR.MINOR.PATCH) - Documente mudanças importantes - Mantenha compatibilidade quando possível ## Recursos Úteis - [MCP Documentation](https://modelcontextprotocol.io) - [Pydantic Documentation](https://docs.pydantic.dev) - [Pytest Documentation](https://docs.pytest.org) - [Python Async/Await](https://docs.python.org/3/library/asyncio.html)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pablicio/my-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server