- Type-annotate _wrap_contact_field signature; drop stale "url" mention
from its docstring (url is handled by the list-coercion helper, not
this one).
- Split the shape-coercion helper so comma-splitting only applies to
categories: _as_str_list (no split) for org/nickname/url,
_split_categories (comma split) for CATEGORIES. Fixes the case where
organization="Smith, Jones & Associates" was mangled into a two-
component ORG.
- Share _normalize_contact_data between create and update so
_merge_vcard_properties only sees canonical keys; add URL handlers in
both update branches so the primary update path no longer drops URL
silently.
- Annotate the Contact(**kwargs) type:ignore with the reason
(pythonvCard4 typeshed doesn't accept **dict[str, Any]).
- Add tests/unit/client/test_contacts.py (pure unit, no HTTP) covering
the #716 round-trip, comma-in-org regression, invalid-bday warning,
tel/phone precedence, categories string-vs-list behaviour, and direct
_normalize_contact_data cases.
- Extend the MCP workflow test with an update-with-url step asserting
the URL handler in _merge_vcard_properties.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
create_contact previously read only fn/email/tel from contact_data and
silently dropped org, organization, note, title, nickname, bday,
categories, url — and didn't accept phone as an alias for tel, so the
reporter's exact call lost every field except fn and email. Introduce
_build_contact_from_data, share it with update_contact's fallback, and
normalise str→list inputs so pythonvCard4 doesn't iterate bare strings
character-by-character for list-typed properties.
Closes#716
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pythonvCard4 parses vCard BDAY fields into datetime.date objects, but
the Contact model expects Optional[str]. This caused a validation error
that crashed the entire contact list. Convert at the client layer
(consistent with the calendar client pattern) with a defensive check
at the server mapping layer.
Closes#672
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix three related contacts bugs:
- Parse dict-format vCard fields ({value, type}) that pythonvCard4 returns,
which previously crashed Pydantic validation expecting plain strings
- Include tel field in client output so phone numbers reach MCP tools
- Clarify addressbook parameter expects URI slug, not displayname
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Prometheus metrics for HTTP, MCP tools, Nextcloud API, OAuth, vector sync, and DB operations
- Add OpenTelemetry distributed tracing with OTLP export
- Add structured JSON logging with trace context correlation
- Add ObservabilityMiddleware for automatic HTTP instrumentation
- Add app_name attribute to all client classes for per-app metrics
- Add configuration for metrics, tracing, and logging via environment variables
- Add documentation in docs/observability.md
- Fix graceful degradation when tracing is disabled (default state)
- Fix uvicorn logging configuration to use observability formatters
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>