feat(notifications): optional Android notification capture → MCP rollup#19
Merged
teslashibe merged 1 commit intomainfrom Apr 22, 2026
Merged
feat(notifications): optional Android notification capture → MCP rollup#19teslashibe merged 1 commit intomainfrom
teslashibe merged 1 commit intomainfrom
Conversation
Adds an opt-in feature that captures device notifications on Android via
NotificationListenerService, ships them to the backend in a Timescale
hypertable, and exposes 5 MCP tools so the agent can produce daily
"what happened / what needs attention" rollups across SMS, WhatsApp,
Zillow, email and any other allowlisted app.
Designed for the real-estate-agent persona but template-generic — every
surface (DB rows, API routes, MCP plugin, system prompt, mobile UI) is
gated by a single NOTIFICATIONS_ENABLED flag (server) and
EXPO_PUBLIC_NOTIFICATIONS_ENABLED (client), default OFF, so forks that
don't need it pay zero overhead.
Backend
- migrations/00003 creates notification_events hypertable + dedup
unique constraint + content FTS index. Inert when feature disabled.
- internal/notifications: store/service/handler with limit clamping,
classification heuristics for action-item ranking, and full-text
search via plainto_tsquery.
- internal/notifications/mcp: 5 tools — list, search, threads, apps,
pending_actions — registered as the credential-less "notifications"
platform.
- internal/mcp: PlatformBinding gains NoCredentials; resolveClient
threads user_id through context for credential-less plugins so the
notification tools can scope queries without a creds blob.
- agent.NotificationsSystemPrompt() appends rollup instructions to the
default prompt — only attached when the flag is on.
- Per-user rate limiter on /notifications/batch.
Mobile (Android)
- Local Expo module modules/notification-capture (Kotlin) implements
NotificationListenerService + SharedPreferences buffer + JS bridge.
- services/notificationSync.ts runs a foreground flush loop on a 5-min
cadence and on every app-foreground transition. On upload failure,
unsent events are requeued into the native buffer via a new
requeueEvents API so flaky cell coverage never drops data.
- providers/NotificationCaptureProvider lifts native state into React
and orchestrates start/stop based on master switch + auth status.
- app/(app)/capture.tsx settings screen with master toggle, permission
CTA, sync stats, allowlist editor (pre-populated with Messages /
WhatsApp / Gmail / Zillow / etc.), and live captured-apps list.
- eas.json with preview + production-apk profiles + build:apk:* npm
scripts so the APK ships via `eas build` without local Android SDK.
Audit + tests
- .cursor/tickets/notification-capture-{scope,audit}.md document intent,
decisions, user stories, ACs, risks, and the ship checklist per
.claude/rules/issue-audit-user-stories.mdc.
- Unit tests cover service classification + ranking, MCP tool schema +
user-id propagation, mcp context plumbing, and platform composition.
- Verification: go vet + go test ./internal/notifications/... clean,
npm run typecheck clean, npx expo prebuild --platform android dry
run succeeds, autolinking confirms the local module is discovered.
Out of scope: iOS (OS-level restriction), background WorkManager sync
(V2), encrypted-at-rest buffer (V2 — needed if we go enterprise).
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in feature that captures device notifications on Android via
NotificationListenerService, ships them to the backend in a Timescale hypertable, and exposes 5 MCP tools so the agent can produce daily "what happened / what needs attention" rollups across SMS, WhatsApp, Zillow, email and any other allowlisted app.Designed for the real-estate-agent persona but template-generic — every surface (DB rows, API routes, MCP plugin, system prompt, mobile UI) is gated by a single
NOTIFICATIONS_ENABLEDflag (server) andEXPO_PUBLIC_NOTIFICATIONS_ENABLED(client), default OFF, so forks that don't need it pay zero overhead.Backend
migrations/00003createsnotification_eventsTimescale hypertable + dedup unique constraint + content FTS index. Inert when feature disabled.internal/notifications: store / service / handler with limit clamping, classification heuristics for action-item ranking, full-text search viaplainto_tsquery.internal/notifications/mcp: 5 tools —list,search,threads,apps,pending_actions— registered as the credential-lessnotificationsplatform.internal/mcp:PlatformBinding.NoCredentials+ context-baseduser_idpropagation so credential-less plugins can scope queries without a creds blob.agent.NotificationsSystemPrompt()appends rollup instructions only when the flag is on./notifications/batch.Mobile (Android)
mobile/modules/notification-capture— KotlinNotificationListenerService+SharedPreferencesbuffer + JS bridge. Autolinking confirmed.services/notificationSync.ts— foreground 5-min flush loop + AppState-driven sync. Network-loss-safe: failed chunks requeue into the native buffer via a newrequeueEventsAPI so flaky cell coverage never drops data.providers/NotificationCaptureProviderlifts native state into React, orchestrates start/stop based on master switch + auth.app/(app)/capture.tsxsettings screen with permission CTA, master toggle, sync stats, allowlist editor (pre-seeded with Messages / WhatsApp / Gmail / Zillow / iMessage / Outlook / Calendar), and live captured-apps stat.eas.jsonwithpreview+production-apkprofiles +npm run build:apk:*scripts so the APK ships viaeas buildwithout local Android SDK.Audit
.cursor/tickets/notification-capture-{scope,audit}.mdperissue-audit-user-stories.mdc— findings, gaps, 5 user stories, full Given/When/Then ACs, risks, ship checklist.flushNowpreviously lost data on network failure (drained store before chunks uploaded). Fixed end-to-end viarequeueEvents.mcp.Server.resolveClientwould 500 on credential-less platforms. Fixed viaNoCredentialsflag + context-based user-id propagation.Out of scope (V2)
iOS (OS-level restriction), background
WorkManagersync, encrypted-at-rest buffer (needed if we go enterprise).Test plan
cd backend && go vet ./...— clean (11 packages)cd backend && go build ./...— cleancd backend && go test ./...— all packages pass (incl. newinternal/notifications/...,internal/mcp/...tests)cd mobile && npm run typecheck— cleancd mobile && npx expo prebuild --platform android --no-install --clean— succeedscd mobile && npx expo-modules-autolinking search --platform android—notification-capturelistedcd mobile && eas build --platform android --profile previewaftereas login(requires cloud)notification_events