Microsoft Planner MCP
Um servidor MCP que conecta assistentes de IA ao Microsoft Planner. Autenticado via Entra ID para autenticação empresarial em nome do usuário.
Documentação
Servidor MCP para Microsoft Planner
Um servidor MCP não oficial que conecta assistentes de IA ao Microsoft Planner. Peça ao seu assistente de IA para criar tarefas, organizar planos, gerenciar buckets e muito mais, tudo por meio de linguagem natural.
Este é um servidor MCP não oficial, comunitário/de código aberto para o Microsoft Planner.
Não é afiliado, endossado ou patrocinado pela Microsoft.
Construído com FastMCP e autenticado via Microsoft Entra ID (Azure AD) usando o fluxo On-Behalf-Of (OBO) para chamar o Microsoft Graph.
Sumário
- O Que Ele Pode Fazer?
- Pré-requisitos
- Configuração do Azure Entra ID
- Instalação
- Configuração
- Executando o Servidor
- Conectando um Cliente MCP
- Ferramentas Disponíveis
- Desenvolvimento
- Contribuindo
- Licença
- Segurança
O Que Ele Pode Fazer?
Depois de conectado, você pode pedir ao seu assistente de IA coisas como:
- "Mostre-me todas as minhas tarefas do Planner que estão atrasadas"
- "Crie uma tarefa chamada 'Preparar relatório do 3º trimestre' no plano de Marketing"
- "Mova todas as tarefas incompletas do bucket Sprint para o bucket Backlog"
- "Marque a tarefa 'Atualizar documentos' como concluída"
O assistente de IA traduz automaticamente sua solicitação nas chamadas de ferramenta apropriadas.
Observação: Este MCP suporta apenas tarefas e planos básicos do Planner
Pré-requisitos
- Uma conta Microsoft 365 com acesso ao Microsoft Planner
- Um registro de aplicativo Azure Entra ID (Azure AD) (veja Configuração do Azure abaixo)
- Um cliente compatível com MCP — por exemplo:
Para executar o servidor localmente, você também precisa de:
- Python 3.12+
- uv (gerenciador de pacotes Python)
Ou, para executar via Docker:
Configuração do Azure Entra ID
Um registro de aplicativo do Azure é necessário para que o servidor possa autenticar usuários e chamar o Microsoft Graph em nome deles. Você precisa de acesso de administrador a um locatário do Azure Entra ID (ou peça ao seu administrador de TI).
Etapa 1 — Registrar o Aplicativo
- Vá para o Portal do Azure → Microsoft Entra ID → Registros de aplicativo → Novo registro
- Digite um nome (por exemplo,
Microsoft Planner MCP) - Em Tipos de conta com suporte, escolha a opção apropriada para sua organização
- Defina o URI de redirecionamento como Web →
http://localhost:8000/auth/callback - Clique em Registrar
Etapa 2 — Configurar Permissões de API
- No seu novo registro de aplicativo, vá para Permissões de API → Adicionar uma permissão → Microsoft Graph → Permissões delegadas
- Adicione estas permissões:
Tasks.ReadWrite— ler e gravar tarefas do PlannerUser.Read— ler o perfil do usuário conectadoUser.ReadBasic.All— resolver nomes de exibição de usuário a partir dos GUIDs nas atribuições de tarefas (necessário apenas para a ferramentalist_users)
- Clique em Conceder consentimento do administrador para sua organização
Etapa 3 — Expor um Escopo de API
- Vá para Expor uma API
- Defina o URI da ID do aplicativo (aceite o padrão
api://<client-id>ou personalize-o) - Clique em Adicionar um escopo:
- Nome do escopo:
mcp-access - Quem pode consentir: Administradores e usuários (ou Somente administradores, se preferir)
- Preencha o nome de exibição e a descrição
- Defina o estado como Habilitado
- Nome do escopo:
Etapa 4 — Definir a Versão do Token
- Vá para Manifesto (ou Autenticação → Configurações avançadas nas versões mais recentes do portal)
- Defina
"requestedAccessTokenVersion"como2 - Salve
Etapa 5 — Criar um Segredo do Cliente
- Vá para Certificados e segredos → Novo segredo do cliente
- Adicione uma descrição e escolha um período de expiração
- Copie o Valor imediatamente (ele é mostrado apenas uma vez)
Etapa 6 — Anote Seus IDs
Você precisará destes três valores para a configuração:
| Valor | Onde Encontrar |
|---|---|
| ID do aplicativo (cliente) | Registro do aplicativo → Visão geral |
| ID do diretório (locatário) | Registro do aplicativo → Visão geral |
| Segredo do cliente | O valor copiado na Etapa 5 |
Instalação
Opção A — Local (Python + uv)
git clone https://github.com/aixolotl/microsoft-planner-mcp
cd microsoft-planner-mcp
uv sync
Opção B — Docker
Nenhuma instalação do Python é necessária. Veja Executando com Docker abaixo.
Configuração
Copie o arquivo de ambiente de exemplo e preencha suas credenciais do Azure:
cp .env.example .env
Edite .env:
# Azure App Registration (required)
CLIENT_ID=your-app-client-id
CLIENT_SECRET=your-app-client-secret
TENANT_ID=your-azure-tenant-id
# Public URL of this server (used for OAuth redirect URI)
BASE_URL=http://localhost:8000
# JSON-encoded list of allowed CORS origins
# Include http://localhost:6274 if using MCP Inspector for testing
ALLOWED_ORIGINS=["http://localhost:8000","http://localhost:6274"]
# Require FastMCP's extra client consent prompt per MCP client. (default: true)
# Prevents confused-deputy attacks by requiring users to explicitly approve each new
# client. Keep true in production; set false only for local dev with throwaway
# clients.
# REQUIRE_AUTHORIZATION_CONSENT=true
# Optional rate-limit overrides (per client sliding window)
# Defaults are 120 requests per 1 minute
# RATE_LIMIT_MAX_REQUESTS=120
# RATE_LIMIT_WINDOW_MINUTES=1
# Optional: keep sign-ins in Redis so they survive restarts (see below)
# REDIS_URL=rediss://:<access-key>@<name>.redis.cache.windows.net:6380/0
# JWT_SIGNING_KEY=<long random string>
# STORAGE_ENCRYPTION_KEY=<Fernet key>
REQUIRE_AUTHORIZATION_CONSENT controla se o FastMCP solicita que os usuários aprovem explicitamente cada novo cliente MCP. Mantenha true (padrão) em produção para evitar ataques de "deputado confuso"; defina como false apenas durante o desenvolvimento local com clientes descartáveis.
RATE_LIMIT_MAX_REQUESTS e RATE_LIMIT_WINDOW_MINUTES são opcionais e devem permanecer comentados, a menos que você precise substituir os padrões do seu ambiente.
Mantendo logins entre reinicializações (Redis)
Por padrão, o servidor mantém o estado OAuth — registros de clientes e tokens Entra dos usuários — em um armazenamento de arquivos criptografado dentro do contêiner. Esse estado é perdido quando o contêiner é recriado ou um Azure Container App é reduzido a zero, e cada réplica tem sua própria cópia. Os clientes então falham ao atualizar com 401 invalid_client e os usuários precisam se reconectar.
Defina REDIS_URL quando você executar mais de uma réplica, reduzir a escala para zero ou recriar contêineres:
| Variável | Padrão | Finalidade |
|---|---|---|
REDIS_URL | não definido (armazenamento de arquivos) | redis:// ou rediss:// (TLS — exigido pelo Azure Redis). O caminho é o índice do banco de dados (por exemplo, /1). Codifique em percentual a senha se ela contiver / (as chaves de acesso do Azure podem conter): python -c "import urllib.parse; print(urllib.parse.quote('<key>', safe=''))". Um Redis não clusterizado é necessário (Redis Cluster suporta apenas o banco de dados 0 e não é suportado). Contém uma credencial: mantenha-a em um segredo. |
JWT_SIGNING_KEY | derivado de CLIENT_SECRET | Segredo fixo (pelo menos 32 caracteres) para assinar os tokens do servidor. Defina-o para que a rotação do segredo do cliente Entra não desconecte todos os usuários. Use o mesmo valor em todas as réplicas. |
STORAGE_ENCRYPTION_KEY | derivado como o padrão do FastMCP | Chave Fernet que criptografa o armazenamento Redis (gere: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"). Exige REDIS_URL — o servidor se recusa a iniciar sem ela. Defina-a explicitamente em produção: o padrão derivado segue o esquema de chave interno do FastMCP, portanto, é tão estável quanto esse esquema. |
Tudo o que é gravado no Redis é criptografado com Fernet. Tokens e transações de login expiram com os tempos de vida do FastMCP (tokens de atualização por 30 dias, transações por minutos), mas os registros de clientes nunca expiram — um é adicionado por registro de cliente MCP, e entradas gravadas sob uma chave antiga permanecem como lixo ilegível. Dê ao armazenamento seu próprio índice de banco de dados (ou seu próprio Redis) em vez de compartilhar um banco de dados com um cache, e use uma política de despejo que não o descarte silenciosamente (noeviction, ou volatile-* com memória suficiente). Um REDIS_URL, JWT_SIGNING_KEY ou STORAGE_ENCRYPTION_KEY inválido falha na inicialização; um valor vazio (JWT_SIGNING_KEY=) conta como não definido. Se o Redis estiver inacessível em tempo de execução, solicitações OAuth e chamadas de ferramenta falham com 500 em cerca de 4 segundos e se recuperam sozinhas quando o Redis volta.
Ativar essas configurações desconecta todos os usuários uma vez. Ativar
REDIS_URL, definirJWT_SIGNING_KEYou alterar qualquer uma das chaves posteriormente invalida logins existentes: clientes conectados recebem um401na próxima chamada ou atualização e precisam se reconectar uma vez. O estado do armazenamento de arquivos existente não é migrado. Planeje a mudança.
Executando o Servidor
Local
uv run uvicorn src.server:app --host 0.0.0.0 --port 8000
O endpoint MCP está disponível em http://localhost:8000/mcp. Um endpoint de verificação de saúde está em http://localhost:8000/health.
Executando com Docker
# Pull the latest image
docker pull ghcr.io/aixolotl/microsoft-planner-mcp:latest
# Run with environment variables
docker run --rm -i \
-e BASE_URL=https://localhost:8000 \
-e CLIENT_ID=your_client_id \
-e CLIENT_SECRET=your_api_token \
-e TENANT_ID=your_tenant_id \
-e ALLOWED_ORIGINS=["http://localhost:8000","http://localhost:6274", "http://localhost:3000"] \
-e REQUIRE_AUTHORIZATION_CONSENT=true \
ghcr.io/aixolotl/microsoft-planner-mcp:latest
Isso inicia o servidor MCP na porta 8000.
Para substituir os limites de taxa, passe também -e RATE_LIMIT_MAX_REQUESTS=<value> e -e RATE_LIMIT_WINDOW_MINUTES=<minutes> (padrões: 120 e 1).
Para manter logins entre recriações de contêiner, passe também -e REDIS_URL=... (veja Mantendo logins entre reinicializações).
Executando com Docker compose
docker compose up
Isso inicia o servidor MCP na porta 8000. A configuração do Docker Compose também inclui uma instância Jaeger para visualização de rastreamento (veja Rastreamento OpenTelemetry).
Conectando um Cliente MCP
Depois que o servidor estiver em execução, configure seu cliente MCP para se conectar a ele.
VS Code
Adicione o seguinte às configurações do seu VS Code (.vscode/settings.json no seu projeto, ou nas configurações do usuário):
{
"mcp": {
"servers": {
"planner": {
"type": "http",
"url": "http://localhost:8000/mcp"
}
}
}
}
Em seguida, use o Copilot Chat no modo Agente e peça para interagir com suas tarefas do Planner. O Copilot descobrirá as ferramentas disponíveis automaticamente.
Outros Clientes MCP
Qualquer cliente que suporte o transporte Streamable HTTP pode se conectar apontando para http://localhost:8000/mcp. O servidor anuncia metadados OAuth automaticamente — o cliente lida com o fluxo de autenticação.
Ferramentas Disponíveis
Todas as ferramentas estão disponíveis para seu assistente de IA automaticamente após a conexão. Você não precisa chamá-las diretamente — basta descrever o que deseja em linguagem natural. Os detalhes dos parâmetros abaixo são fornecidos para referência e para desenvolvedores de clientes.
Ferramentas somente leitura são anotadas com readOnlyHint: true para que os clientes possam pular prompts de confirmação. Ferramentas destrutivas (exclusões) são anotadas com destructiveHint: true.
Usuário
get_me
Retorna o perfil do usuário autenticado do Microsoft Graph.
- Parâmetros: Nenhum
- Retorna: Objeto de perfil do usuário (id, displayName, mail, etc.) ou
null
list_users
Recupera usuários do Microsoft 365 por GUID, endereço de e-mail ou pesquisa de texto livre. Útil para resolver os GUIDs de usuário retornados nos objetos de atribuição de tarefas para nomes de exibição.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
select | string | Não | Campos separados por vírgula a incluir (padrão: id,displayName,mail,userPrincipalName). Passe *all para todos os campos. |
search | string | Não | Pesquisa de texto livre no nome de exibição, ou pesquisa por nome e valor do campo, por exemplo, Alice ou surname:Smith |
guids | list[string] | Não | GUIDs de objetos de usuário para consultar. Traduzido para uma expressão OData $filter. |
emails | list[string] | Não | Nomes principais de usuário (UPNs / endereços de e-mail) para consultar. Traduzido para uma expressão OData $filter. |
top | integer | Não | Número máximo de usuários a retornar (padrão: 10). Ignorado quando guids ou emails são fornecidos. |
- Retorna: Lista de objetos de usuário ou
null
Observação: Quando tanto
guids/emailsquantosearchsão fornecidos, o filtro de GUID/e-mail tem prioridade. Listas muito grandes de GUIDs ou endereços de e-mail são silenciosamente truncadas para permanecer dentro do limite de URL do Graph de 2.048 caracteres.
Grupos
list_my_groups
Lista todos os grupos do Microsoft 365 dos quais o usuário autenticado é membro.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
select | string | Não | Campos separados por vírgula a incluir (padrão: id,displayName,mail). Passe *all para todos os campos. |
filter | string | Não | Expressão de filtro OData, por exemplo, startsWith(displayName,'Project') |
search | string | Não | String de pesquisa OData, por exemplo, "displayName:Project" |
- Retorna: Lista de objetos de grupo ou
null
Observação: A ferramenta Grupos não retornará detalhes como nome ou e-mail de grupos com as permissões padrão
Tasks.ReadWritedocumentadas aqui, no entanto pesquisa e filtragem ainda funcionam, então você pode encontrar um grupo com um nome específico usando esta ferramenta.
Planos
list_my_plans
Lista os planos do Planner compartilhados com o usuário autenticado.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
select | string | Não | Campos separados por vírgula a incluir (padrão: id,title,owner,createdBy,createdDateTime). Passe *all para todos os campos. |
- Retorna: Lista de objetos de plano ou
null
list_group_plans
Lista todos os planos do Planner pertencentes a um grupo do Microsoft 365.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
groupId | string | Sim | O ID do objeto do grupo (de list_my_groups) |
select | string | Não | Campos separados por vírgula para incluir (padrão: id,title,owner,createdBy,createdDateTime). Passe *all para todos os campos. |
- Retorna: Lista de objetos de plano ou
null
create_plan
Cria um novo plano do Planner para um grupo do Microsoft 365.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
groupId | string | Sim | O ID do objeto do grupo M365 que será o proprietário do plano |
title | string | Sim | Título de exibição para o novo plano |
- Retorna: O objeto de plano criado ou
null
delete_plan
Exclui um plano do Planner.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
planId | string | Sim | O ID do plano a ser excluído |
etag | string | Sim | O @odata.etag atual do plano (tenta novamente uma vez se estiver desatualizado) |
- Retorna: Mensagem de confirmação
list_plan_categories
Obtém as definições de rótulos de categoria para um plano do Planner. Retorna todos os 25 slots de categoria com sua chave (ex.: category1) e nome de exibição.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
planId | string | Sim | O ID do plano |
- Retorna: Lista de objetos de categoria (
key,display_name) ounull
Buckets
list_buckets
Lista todos os buckets em um plano do Planner.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
planId | string | Sim | O ID do plano |
- Retorna: Lista de objetos de bucket ou
null
create_bucket
Cria um novo bucket em um plano do Planner.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
planId | string | Sim | O ID do plano no qual criar o bucket |
name | string | Sim | Nome de exibição para o novo bucket |
- Retorna: O objeto de bucket criado ou
null
delete_bucket
Exclui um bucket do Planner.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
bucketId | string | Sim | O ID do bucket a ser excluído |
etag | string | Sim | O @odata.etag atual do bucket (tenta novamente uma vez se estiver desatualizado) |
- Retorna: Mensagem de confirmação
Tarefas
list_my_tasks
Lista todas as tarefas do Planner atribuídas ao usuário autenticado em todos os planos.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
select | string | Não | Campos separados por vírgula para incluir (padrão: *all). Passe *all para todos os campos. |
filter | string | Não | Expressão de filtro OData, ex.: percentComplete ne 100 |
search | string | Não | Pesquisa de texto livre correspondente ao título da tarefa |
- Retorna: Lista de objetos de tarefa ou
null
list_tasks
Lista todas as tarefas em um plano do Planner.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
planId | string | Sim | O ID do plano |
select | string | Não | Campos separados por vírgula para incluir (padrão: *all). Passe *all para todos os campos. |
filter | string | Não | Expressão de filtro OData, ex.: percentComplete eq 0 |
search | string | Não | Pesquisa de texto livre correspondente ao título da tarefa |
- Retorna: Lista de objetos de tarefa ou
null
get_task_details
Obtém os detalhes completos de uma tarefa: descrição, itens de lista de verificação e referências externas.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
taskId | string | Sim | O ID da tarefa |
- Retorna: Objeto de detalhes da tarefa (descrição, lista de verificação, referências) ou
null
create_task
Cria uma nova tarefa em um plano do Planner.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
planId | string | Sim | O ID do plano |
bucketId | string | Sim | O ID do bucket no qual colocar a tarefa |
title | string | Sim | Título da tarefa |
startDateTime | string | Não | Data de início ISO 8601 (ex.: 2026-05-01T00:00:00) |
dueDateTime | string | Não | Data de vencimento ISO 8601 (ex.: 2026-05-31T00:00:00) |
percentComplete | inteiro | Não | Percentual de conclusão, 0–100 |
assignUserIds | lista[string] | Não | IDs de objeto de usuário para atribuir à tarefa |
- Retorna: O objeto de tarefa criado ou
null
update_task
Atualiza os campos padrão e/ou campos de detalhes de uma tarefa (descrição, lista de verificação, referências). Somente os campos fornecidos são alterados. Quando campos de detalhes são especificados, uma chamada de API separada atualiza automaticamente o recurso de detalhes da tarefa.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
taskId | string | Sim | O ID da tarefa |
etag | string | Sim | O @odata.etag atual da tarefa (tenta novamente uma vez se estiver desatualizado) |
title | string | Não | Novo título |
percentComplete | inteiro | Não | Percentual de conclusão, 0–100 |
dueDateTime | string | Não | Data de vencimento ISO 8601 |
bucketId | string | Não | ID do bucket para mover a tarefa |
priority | inteiro | Não | Prioridade 0 (urgente)–10 (baixa). O Planner mapeia: 1=urgente, 3=importante, 5=média, 9=baixa |
assigneePriority | string | Não | Dica de ordem para classificação na lista de tarefas do responsável |
appliedCategories | objeto | Não | Categorias por slot de categoria (ex.: {"category3": true}). Use list_plan_categories para nomes de rótulos |
assignUserIds | lista[string] | Não | IDs de usuário para atribuir |
unassignUserIds | lista[string] | Não | IDs de usuário para remover |
description | string | Não | Descrição em texto simples (até 2000 caracteres) — campo de detalhes |
previewType | string | Não | Estilo de visualização: automatic, noPreview, checklist, description ou reference — campo de detalhes |
checklistItems | objeto | Não | Dicionário por GUID de item da lista de verificação. Passe null como chave para excluir esse item — campo de detalhes |
references | objeto | Não | Dicionário por URL de referência codificada. Passe null como chave para excluir essa referência — campo de detalhes |
etagDetails | string | Não | O @odata.etag do recurso de detalhes da tarefa. Obrigatório somente ao atualizar campos de detalhes; atualizado automaticamente do Graph se omitido. |
- Retorna: O objeto de tarefa atualizado, o objeto de detalhes da tarefa atualizado ou ambos (
{ "task": ..., "details": ... }), dependendo de quais campos foram fornecidos. Retornanullse o resultado estiver vazio.
Nota: A ferramenta
update_task_detailsfoi removida. Useupdate_taskcom parâmetros de campos de detalhes (description,checklistItems,references,previewType) em vez disso. Os recursos de tarefa e detalhes têm ETags separados — forneçaetagDetailsquando disponível para evitar uma ida e volta extra, ou omita-o para permitir que a ferramenta atualize automaticamente.
list_task_fields
Retorna metadados para cada campo em uma tarefa do Planner, incluindo seu tipo de dados, descrição, se é gravável e se requer uma chamada separada de get_task_details.
- Parâmetros: Nenhum
- Retorna: Lista de objetos de metadados de campo (
name,type,description,writable,detailed)
Nota: A lista de campos é estática, pois o Planner Standard não suporta campos personalizados. Mas este endpoint aguarda as futuras atualizações da Microsoft na API para suportar totalmente o Planner Premium.
delete_task
Exclui uma tarefa do Planner.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
taskId | string | Sim | O ID da tarefa |
etag | string | Sim | O @odata.etag atual da tarefa (tenta novamente uma vez se estiver desatualizado) |
- Retorna: Mensagem de confirmação
Desenvolvimento
Esta seção cobre construção, testes e contribuição para o projeto.
Estrutura do Projeto
src/
├── server.py # FastMCP app, middleware, route mounting
├── config.py # Settings via pydantic-settings
├── auth_provider.py # Azure OAuth provider (OBO flow) + optional Redis state store
├── deps.py # Shared dependency helpers
├── graph_client_manager.py # Singleton GraphClientManager with per-user OBO clients
├── telemetry.py # OpenTelemetry setup
├── types.py # Shared structural types
├── services/
│ └── planner_service.py # Business logic wrapping Graph SDK calls
└── tools/
├── me.py # get_me
├── groups.py # list_my_groups
├── plans.py # plan tools + list_plan_categories
├── tasks.py # task tools + list_task_fields
├── buckets.py # bucket tools
└── users.py # list_users
tests/
├── conftest.py
├── test_auth_provider.py
├── test_buckets_tool.py
├── test_config.py
├── test_groups_tool.py
├── test_planner_service.py
├── test_plans_tool.py
├── test_redis_storage_integration.py # needs a real Redis (REDIS_TEST_URL)
├── test_tasks_tool.py
└── test_users_tool.py
Instalando Dependências de Desenvolvimento
uv sync --dev
Executando Testes
uv run pytest
Com saída detalhada:
uv run pytest -v
tests/test_redis_storage_integration.py é executado contra um Redis real e é ignorado a menos que REDIS_TEST_URL esteja definido (o CI fornece um serviço redis:7-alpine). Aponte-o para um banco de dados de teste — os testes gravam e excluem suas próprias chaves:
docker run -d --rm --name redis-test -p 6379:6379 redis:7-alpine
REDIS_TEST_URL=redis://localhost:6379/15 uv run pytest tests/test_redis_storage_integration.py -v
Arquitetura
O servidor é construído com FastMCP e usa estes padrões principais:
Composição do servidor — As ferramentas são divididas em cinco roteadores de domínio (me, groups, plans, tasks, buckets), cada um uma instância independente de FastMCP montada no aplicativo principal. Isso mantém as ferramentas, importações e testes de cada domínio isolados.
Autenticação — O servidor usa o padrão OAuthProxy do FastMCP por meio de um AzureProvider personalizado. O Azure Entra ID não suporta Registro Dinâmico de Cliente (DCR), então o provedor atua como um proxy compatível com DCR voltado para clientes MCP enquanto usa as credenciais de aplicativo pré-registradas com o Azure. Quando uma chamada de ferramenta chega, o servidor troca o token de sessão MCP por um token do Microsoft Graph por meio do fluxo On-Behalf-Of, com escopo para Tasks.ReadWrite, User.Read e User.ReadBasic.All. O estado do proxy (registros de clientes, tokens Entra dos usuários) reside no armazenamento de arquivos criptografado do FastMCP por padrão, ou em um armazenamento Redis criptografado quando REDIS_URL está definido — consulte Mantendo logins entre reinicializações.
Middleware — Cinco camadas de middleware integrado são empilhadas no servidor (mais externa primeiro):
| Middleware | Finalidade |
|---|---|
ErrorHandlingMiddleware | Captura exceções não tratadas, registra rastreamentos completos no lado do servidor, retorna erros MCP limpos |
SlidingWindowRateLimitingMiddleware | 60 req/min por cliente para proteger a cota do Microsoft Graph |
TimingMiddleware | Registra a duração em tempo real para cada operação MCP |
StructuredLoggingMiddleware | Emite uma linha de log JSON por solicitação (método, status, duração, informações do cliente) |
ResponseLimitingMiddleware | Trunca respostas de ferramentas acima de 500 KB para evitar estouro dos contextos de janela do LLM |
Registro de cliente — Cada ferramenta envia mensagens de progresso em tempo real ao cliente MCP por meio de get_optional_context(), para que os usuários vejam atualizações de status como "Buscando tarefas…" e "Encontradas 12 tarefa(s)" em seu cliente.
Rastreamento OpenTelemetry
O servidor inclui instrumentação OpenTelemetry nativa com zero sobrecarga quando não utilizada. Para habilitar a exportação de rastreamento:
# Install the optional OTEL dependency group
uv sync --group otel
# Set the OTLP endpoint and start the server
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_SERVICE_NAME=microsoft-planner-mcp
uv run uvicorn src.server:app --host 0.0.0.0 --port 8000
Os rastreamentos seguem as convenções semânticas MCP e funcionam com qualquer backend compatível com OTLP (Jaeger, Grafana Tempo, Datadog, New Relic, etc.). A configuração do Docker Compose inclui Jaeger com uma interface em http://localhost:16686.
Testando com o MCP Inspector
O MCP Inspector permite testar ferramentas interativamente em um navegador:
-
Inicie o servidor:
uv run uvicorn src.server:app --host 0.0.0.0 --port 8000 -
Em um terminal separado:
npx @modelcontextprotocol/inspector -
Abra a URL impressa no terminal (ex.:
http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=...) -
Defina Tipo de Transporte como
Streamable HTTPe URL comohttp://localhost:8000/mcp, depois clique em Conectar.
Contribuindo
Contribuições são bem-vindas! Abra uma issue ou envie um pull request. Consulte os modelos de issue para relatórios de bugs, solicitações de recursos e tarefas.
Licença
Este projeto é licenciado sob a Licença MIT.