Skip to content

ADR-0001: Shared Python Library for Cross-Cutting Concerns#

Status: Accepted Date: 2026-04-24

Context#

As the ai-connectors repo has grown from one to three MCP servers (SharePoint, Outlook, Teams) with more planned, identical code has been copy-pasted across each service:

  • auth.pyOBOAuthenticator (Azure AD On-Behalf-Of flow + MSAL) and the DynamoDB token cache via py-key-value-aio are duplicated verbatim in every connector (e.g., connectors/mcps/sharepoint/src/sharepoint_mcp/auth.py, connectors/mcps/outlook-mcp/src/outlook_mcp/auth.py)
  • Audit logginglog_tool_call helper and the Kinesis Firehose sink are duplicated across all tools
  • Structured logging setuplogging.basicConfig / structlog configuration is repeated in each server.py

With three connectors today and 3–5 more planned, per-service drift is already a risk. A bug fix in OBOAuthenticator must be applied to every connector individually. An audit schema change must be coordinated across all services simultaneously — creating real risk of audit inconsistency in a regulated environment.

Decision#

Extract shared code into connectors/libs/nn-mcp-core/ as a uv-managed internal package. MCP servers declare it as a path dependency in their pyproject.toml:

dependencies = [
    "nn-mcp-core",
    ...
]

[tool.uv.sources]
nn-mcp-core = { path = "../../libs/nn-mcp-core", editable = true }

The repo root will manage a uv workspace with a single uv.lock shared across all connectors and the library, ensuring version consistency.

Initial contents of nn-mcp-core: - auth.pyOBOAuthenticator with DynamoDB token cache - audit.pylog_tool_call + Firehose sink - logging.py — shared structured logging setup

Consequences#

Positive#

  • Single source of truth for auth and audit code — a bug fix or security patch applies once and reaches all connectors on their next build
  • Audit schema consistency is enforced at the library level; connectors cannot silently diverge
  • Less boilerplate per new MCP server — the Copier template can depend on nn-mcp-core out of the box
  • Easier to test shared logic centrally with a full test suite

Negative#

  • Versioning overhead — breaking changes to the library have a repo-wide blast radius and require coordinated updates across all consumers
  • More complex local development — developers must understand the uv workspace setup and editable installs
  • Library changes are not automatically reflected in deployed services; each MCP must be rebuilt and redeployed

Risks#

  • CI/CD rebuild detection: When only connectors/libs/nn-mcp-core/ changes, the deploy workflows for individual MCPs will not trigger automatically. Mitigation: rebuild all MCPs on any change to connectors/libs/ (conservative approach — acceptable given the small number of MCPs and the shared blast radius of library changes).
  • Versioning discipline: Without a clear versioning policy, breaking changes could be introduced silently. Mitigation: treat the library as semver-versioned internally; document breaking changes in a CHANGELOG.

Alternatives Considered#

Option B: Keep per-service copies (status quo)#

Continue duplicating auth.py and audit helpers in each MCP server.

Rejected because: Drift is already observable with three services. A security fix in OBOAuthenticator (e.g., token refresh logic, cache key collision) must be applied and tested in every connector. Audit schema inconsistency in a regulated environment is a compliance risk, not just a maintenance burden. This approach does not scale to 5–8 connectors.

  • GitHub issue #104 — original discussion and decision thread
  • connectors/mcps/sharepoint/src/sharepoint_mcp/auth.py — current copy
  • connectors/mcps/outlook-mcp/src/outlook_mcp/auth.py — current copy
  • connectors/mcps/teams/src/teams_mcp/auth.py — current copy (if present)