Juggling dozens of clients? ClientPulse gives you a morning briefing, preps every call, and warns you when a client's gone quiet, right through Alexa+.
🚧 In development — Amazon Developer Hackathon 2026 (Alexa+ Track + AWS Builder mini-challenge)
Freelancers rarely lose clients because they lack a CRM. They lose them because important signals — CRM notes, invoices, meetings, commitments — are scattered across tools and easy to miss. ClientPulse turns those scattered signals into the one next action a freelancer needs to take, voice-first, through Alexa+.
- Daily briefing — what needs your attention today, in one glance
- Call prep — instant context before any client call, plus an AI-generated priority explanation and recommended next action
- Invoice tracking — never miss an overdue payment
- Stale client detection — spot relationships going quiet before they go cold
- Persistent memory — remembers commitments and notes across sessions
BACKGROUND (precomputed, not on the voice request path)
GHL + Invoice data → Rules-based relationship scoring → Strands Agent → Amazon Bedrock (Nova Micro) → Client insight → Storage (cached)
RUNTIME (fast path, <500ms target for Alexa+)
Alexa+ → FastMCP (Streamable HTTP) → Storage read → Structured response → Alexa+ voice response + MCP App visual card
This separation is the core technical decision behind ClientPulse: Alexa+ MCP requests read pre-scored, pre-cached data instead of calling an LLM live, keeping latency low while still delivering AI-generated insight.
- MCP spec: 2025-11-25+
- Transport: Streamable HTTP
- Server: Python + FastMCP
- Verified end-to-end with the official MCP Inspector (5 tools, all discoverable and callable)
from fastmcp import FastMCP
mcp = FastMCP(name="ClientPulse", ...)
@mcp.tool()
def prep_call(client_id: str) -> dict:
...- Amazon Bedrock (Nova Micro) — client insight generation: prioritization,
plain-language explanation, and one recommended next action. The model
never computes the priority score itself — that's deterministic, rules-based
scoring (see
src/scoring/relationship.py) — the model only explains and prioritizes. - AWS Strands Agents SDK — orchestrates the client-intelligence pipeline
(
src/agent/client_intelligence.py) - Amazon DynamoDB — persistent client memory backend for production deployment (single-table design), swappable with local storage for fully offline development and demo (see Storage Backend below)
- AWS Lambda — MCP server hosting, container-based deployment with AWS Lambda Web Adapter for Streamable HTTP support
- Amazon Cognito — OAuth 2.1 (client_credentials grant) for MCP service-level authentication, verified end-to-end
Rather than a single LLM call, ClientPulse separates deterministic logic from AI reasoning:
- Python (rules): days-silent calculation, invoice-overdue check, meeting-proximity check, relationship score
- Bedrock (via Strands): turns those signals into a human-readable explanation and one concrete recommended action
This keeps cost and latency low, avoids hallucinated priority scores, and
keeps every recommendation explainable and auditable. A mock model
(src/agent/mock_model.py) mirrors this exact contract for offline
development — the real Bedrock call is a single environment variable away.
| Tool | Description |
|---|---|
get_daily_briefing() |
Today's overview: clients needing attention, overdue invoices, upcoming calls |
prep_call(client_id) |
Full call-prep context + AI-generated insight |
check_invoices() |
All overdue invoices with client names and amounts |
list_stale_clients() |
Clients with no meaningful contact in 7+ days |
record_client_note(client_id, note) |
Persist a note/commitment, remembered in future prep_call calls |
clientpulse-mcp/
├── LICENSE
├── README.md
├── requirements.txt
├── Dockerfile
├── .env.example
├── src/
│ ├── server.py # FastMCP entrypoint, registers all 5 tools
│ ├── tools/ # One file per MCP tool
│ ├── integrations/
│ │ ├── ghl.py # CRM provider interface + demo data
│ │ └── invoices/ # Invoice provider interface + demo data
│ ├── storage/
│ │ ├── store.py # Backend switcher (local/dynamodb)
│ │ ├── local_json.py # Local JSON storage (default)
│ │ └── dynamodb.py # AWS DynamoDB storage
│ ├── scoring/
│ │ └── relationship.py # Rules-based relationship scoring
│ ├── agent/
│ │ ├── client_intelligence.py # Strands + Bedrock orchestration
│ │ ├── mock_model.py # Offline-dev mock for the AI layer
│ │ └── prompts.py
│ └── models/
├── workers/
│ └── sync_and_analyze.py # EventBridge-ready background sync
├── apps/
│ └── briefing/
│ └── app.html # MCP App visual card
├── tests/ # pytest suite, 15 tests
└── docs/
├── architecture.md
├── demo-script.md
├── product-feedback.md
└── friction-log.md
This build uses seeded demo data behind clean provider interfaces:
- CRM (GHL):
GHLProviderinterface withDemoGHLProviderimplementation (5 realistic seeded clients). A real adapter (RealGHLProvider) can be added using the GHL REST API without changing any calling code. - Invoices:
InvoiceProviderinterface withDemoInvoiceProvider. Production adapters can connect to Stripe, QuickBooks, or GHL invoicing.
This keeps the demo self-contained and reproducible for judges while making the production integration point explicit.
ClientPulse supports two interchangeable storage backends via the
STORAGE_BACKEND environment variable:
local(default) — JSON file storage (data/local_store.json). Zero external dependencies; the entire product runs and demos with no AWS account required.dynamodb— Amazon DynamoDB, single-table design, for production or AWS-hosted deployment.
Both backends implement identical function signatures
(src/storage/store.py is the switcher), so calling code — every MCP
tool and the background worker — never needs to know which is active.
git clone https://github.com/ShArafat58/ClientPulse.git
cd ClientPulse
pip install -r requirements.txt
cp .env.example .envBy default (STORAGE_BACKEND=local), no further setup is needed — run the
server directly:
python -m src.serverOptional — using DynamoDB instead of local storage:
docker run -d -p 8001:8000 --name clientpulse-dynamodb amazon/dynamodb-local
python scripts/create_table.pyThen set STORAGE_BACKEND=dynamodb in .env.
Verify with MCP Inspector:
npx @modelcontextprotocol/inspectorConnect to http://localhost:8000/mcp via Streamable HTTP.
The MCP server is containerized for AWS Lambda using the AWS Lambda Web Adapter:
docker build -t clientpulse-mcp .
docker tag clientpulse-mcp:latest <ecr-repo-uri>:latest
docker push <ecr-repo-uri>:latest
aws lambda create-function --function-name clientpulse-mcp \
--package-type Image --code ImageUri=<ecr-repo-uri>:latest \
--role <lambda-execution-role-arn> --timeout 30 --memory-size 512 \
--environment "Variables={STORAGE_BACKEND=dynamodb,USE_MOCK_BEDROCK=false}"pytest tests/ -vBuilt to stay within AWS Free Tier / Free Plan credits when deployed on AWS:
- DynamoDB: pay-per-request, always-free tier covers demo-scale usage
- Lambda: well within the 1M free requests/month allowance
- Bedrock: capped at
MAX_BEDROCK_CALLS_PER_SYNC=20per background sync - No NAT Gateway, no provisioned concurrency, no always-on compute
- The local storage backend means the full product can also be built, tested, and demoed with $0 cloud spend
- OAuth 2.1 (client_credentials grant) via Amazon Cognito for service-level authentication
- No real client data used — all CRM and invoice data is seeded/demo data
- AWS credentials are never committed;
.envis gitignored
Built during the Amazon Developer Hackathon 2026 (Sept–Oct 2026) as a solo
entry. See docs/friction-log.md for a full log of issues encountered and
worked around during development, including new-AWS-account access
restrictions that motivated the swappable storage/model design.
See docs/product-feedback.md.
See docs/friction-log.md.
MIT — see LICENSE.