Skip to content

fix(integrations): PUT de configuração grava só os campos editáveis (CRM-873) - #85

Merged
daniloleonecarneiro merged 7 commits into
developfrom
fix/CRM-873-put-config-integracoes
Nov 6, 2026
Merged

daniloleonecarneiro merged 7 commits into
developfrom
fix/CRM-873-put-config-integracoes

Conversation

@daniloleonecarneiro

@daniloleonecarneiro daniloleonecarneiro commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

O PUT de configuração das integrações MCP (asana, atlassian, canva, github, hubspot, linear, monday, notion, paypal e supabase) passa a gravar só os campos que o usuário edita. Ele parte da config gravada e copia do corpo apenas os campos de uma lista fechada por provedor (EDITABLE_CONFIG_FIELDS em mcp_integration_base.py), que hoje é tools nos dez. O resto da config continua sendo escrito pelo fluxo de OAuth. O connected segue calculado a partir do token gravado.

  • Campos fora da lista são ignorados, não rejeitados. Os dialogs do front reenviam a config que receberam do GET junto com as tools selecionadas, e um 422 quebraria o Salvar.
  • O corpo passa a ser um modelo (IntegrationConfigUpdate). Um config que não é objeto devolve 422 em vez de responder 200 sem salvar nada.
  • Google Calendar e Sheets já tinham o corpo fechado por um modelo pydantic. Nesses dois entrou só o teste.

Estado de OAuth direto no banco. O verifier de PKCE, as credenciais do client registrado, os tokens do callback e do refresh das integrações MCP, e as linhas google_*_credentials, eram salvos pelo POST /agents/:id/integrations do core sempre que não havia sessão. O core passa a ignorar campos de OAuth nessa rota (evo-ai-core-service-community#56), então essas escritas vão para o banco (write_integration_config em agent_service.py), abrindo sessão quando o chamador não tem uma. Escrita que falha levanta erro em vez de cair no core, e a leitura antes de cada read-modify-write (read_integration_config) levanta em erro de banco em vez de ler como config vazia.

Ordem de merge: 1. a migration que dá default a evo_core_agent_integrations.tenant_id nas gems de licensing · 2. esta · 3. evo-ai-core-service-community#56.

Test plan

  • pytest tests/unit --ignore=tests/unit/test_exception_handlers.py (como no CI): 846 passed
  • tests/unit/services/test_oauth_state_writes.py (novo): 45 passed. Nos dez serviços MCP, PKCE, client credentials e tokens vão para o banco sem nenhuma chamada HTTP; nos dois Google, as credenciais também; escrita que falha levanta; leitura com erro de banco levanta.
  • tests/unit/test_integration_config_put.py (novo): 54 passed. Por provedor, campos fora da lista não alteram a config gravada, tools salva como antes e config que não é objeto devolve 422. Nos dois Google, os campos fora do modelo não chegam à config gravada.
  • Mutações, todas vermelhas:
    • helper copiando o corpo inteiro
    • modelo do Google Calendar com extra: allow
    • corpo do notion de volta para Dict[str, Any]
    • mcp_oauth_service.py e os serviços Google da develop contra os testes novos: 42 falhas
    • leitura engolindo o erro de banco
  • Ao vivo, com o router real de cada provedor e o Postgres (tabela da migration 000014 do core), só auth/permissão e service stubados. Na develop, o PUT altera os campos fora da lista; nesta branch, não altera, e tools salva nos dez.
  • Ao vivo, sem sessão do chamador e com o cliente HTTP do core levantando em qualquer chamada: PKCE gravado, client credentials preservando o PKCE, PKCE apagado, tokens do callback e credenciais do Google, todos lidos de volta do Postgres.
  • Mesclada com a fix(integrations): error paths answer their own status, without the exception text (CRM-874) #86 (mesmos arquivos de rota): sem conflito, 854 passed.

…(CRM-873)

The PUT merged the whole request body over the stored config and only
kept the sensitive fields the body left out, so a body carrying
access_token, refresh_token, client_secret, pkce_verifiers,
oauth_metadata, mcp_url or headers replaced them. The ten MCP
integrations now take only their closed list of editable fields
(tools) from the body; the rest is written by the OAuth callback.
@sourcery-ai

sourcery-ai Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

O PR corrige a gravação de configuração das integrações ao trocar o merge amplo do corpo por uma lista fechada de campos editáveis. Para os dez provedores MCP, apenas tools pode ser alterado pelo PUT; credenciais e metadados permanecem provenientes do callback OAuth, enquanto os endpoints Google continuam protegidos pelos modelos Pydantic. Novos testes cobrem tanto a rejeição silenciosa de campos sensíveis quanto a manutenção do comportamento editável.

Sequence diagram for the protected integration configuration PUT

sequenceDiagram
    participant Client
    participant ProviderRoute
    participant merge_editable_config
    participant ConfigStore

    Client->>ProviderRoute: PUT configuration
    ProviderRoute->>ConfigStore: Load stored_config
    ConfigStore-->>ProviderRoute: stored_config
    ProviderRoute->>merge_editable_config: merge_editable_config(provider, stored_config, incoming)
    merge_editable_config-->>ProviderRoute: stored_config plus incoming tools only
    ProviderRoute->>ProviderRoute: Set connected from access_token
    ProviderRoute->>ConfigStore: Save updated_config
    ConfigStore-->>Client: Updated configuration
Loading

File-Level Changes

Change Details Files
Restringe o PUT das dez integrações MCP aos campos explicitamente editáveis, preservando credenciais, metadados OAuth e demais dados gravados.
  • Adiciona EDITABLE_CONFIG_FIELDS, atualmente contendo apenas tools para cada provedor.
  • Centraliza o merge em merge_editable_config, ignorando chaves extras do corpo sem retornar 422.
  • Mantém connected derivado da presença de access_token.
  • Substitui a lógica de merge anterior nos handlers de Asana, Atlassian, Canva, GitHub, HubSpot, Linear, Monday, Notion, PayPal e Supabase.
src/api/mcp_integration_base.py
src/api/asana_routes.py
src/api/atlassian_routes.py
src/api/canva_routes.py
src/api/github_routes.py
src/api/hubspot_routes.py
src/api/linear_routes.py
src/api/monday_routes.py
src/api/notion_routes.py
src/api/paypal_routes.py
src/api/supabase_routes.py
Adiciona cobertura de testes para impedir sobrescrita de credenciais e confirmar a persistência dos campos permitidos.
  • Testa, em todos os dez provedores MCP, que credenciais, tokens, PKCE, OAuth, URL, headers e connected não são alterados por valores forjados.
  • Testa que tools continua sendo salvo, inclusive quando o corpo contém a configuração ecoada pelo frontend.
  • Verifica que Calendar e Sheets descartam campos de credencial via seus modelos Pydantic e preservam somente os campos suportados.
tests/unit/test_integration_config_put.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/api/mcp_integration_base.py" line_range="51" />
<code_context>
+    incoming: Optional[Dict[str, Any]],
+) -> Dict[str, Any]:
+    """Stored config with only the provider's editable fields taken from the request body."""
+    incoming = incoming if isinstance(incoming, dict) else {}
+    editable = {k: incoming[k] for k in EDITABLE_CONFIG_FIELDS[provider] if k in incoming}
+    return {**stored_config, **editable}
</code_context>
<issue_to_address>
**Malformed config reports successful save**

When the request's nested `config` value is not a JSON object, `merge_editable_config` replaces a non-object `incoming` value with `{}`, so the route persists the unchanged configuration and returns success. Callers sending a malformed `config` value see a successful save even though their changes were discarded.

Reject a non-object nested config with a client error instead of silently treating it as an empty update.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: src/api/mcp_integration_base.py:51


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/api/mcp_integration_base.py Resolved
…object (CRM-873)

The body is now a model with a config object, so a list, string or
null config is a 422 instead of a silent no-op. The editable-fields
comment no longer lists headers, which the stored config never holds.
sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Nov 6, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

…h the core API (CRM-873)

PKCE verifiers, registered client credentials, tokens from the callback
and refreshed tokens of the MCP integrations, and the Google credential
rows, were saved through POST /agents/:id/integrations of the core
whenever no session was passed. The core now ignores OAuth fields sent
to that route, so these writes go to the database, opening a session
when the caller has none, and a failed write raises instead of falling
back. The read before each read-modify-write raises on a database error
rather than reading as an empty config.
@sourcery-ai
sourcery-ai Bot dismissed their stale review November 6, 2026 14:07

Sourcery withdrew this approval because the latest commits introduced blocking findings.

@daniloleonecarneiro
daniloleonecarneiro merged commit a425da2 into develop Nov 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant