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.py—OBOAuthenticator(Azure AD On-Behalf-Of flow + MSAL) and the DynamoDB token cache viapy-key-value-aioare 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 logging —
log_tool_callhelper and the Kinesis Firehose sink are duplicated across all tools - Structured logging setup —
logging.basicConfig/structlogconfiguration is repeated in eachserver.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.py — OBOAuthenticator with DynamoDB token cache
- audit.py — log_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-coreout 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 toconnectors/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.
Related#
- GitHub issue #104 — original discussion and decision thread
connectors/mcps/sharepoint/src/sharepoint_mcp/auth.py— current copyconnectors/mcps/outlook-mcp/src/outlook_mcp/auth.py— current copyconnectors/mcps/teams/src/teams_mcp/auth.py— current copy (if present)