tif-score-mcp
# TIF Score MCP
An MCP (Model Context Protocol) server that exposes three tools to any MCP-compatible client (Claude Desktop, Claude Code, or anything else that speaks MCP):
- **`score_conversation`** — analyzes a call transcript and returns structured business intelligence.
- **`get_lead_score`** — scores a transcript 0-100 and classifies it hot / warm / cold.
- **`diagnose_workflow_error`** — given an n8n workflow's JSON and an error message, returns a root cause and a proposed minimal fix.
This is a port, not a rewrite: the scoring prompt and weighting logic are adapted from a real, working conversation-analysis and lead-scoring pipeline (company and product names generalized for this public repo), and the workflow-diagnosis tool is a scoped-down port of a larger workflow self-healing system's diagnostic reasoning. See [`docs/BTS.md`](docs/BTS.md) for a plain-language writeup of the whole process, from planning through finished server.
## Demo
Real MCP server, real Claude API calls, real output — no mocked data. Generated by running [`demo/run-demo.mjs`](demo/run-demo.mjs) (`npm run demo`):

A produced walkthrough is also available: [`demo/promo/tif-score-mcp-promo.mp4`](demo/promo/tif-score-mcp-promo.mp4) (46s) — why this exists, the three tools, and the same real terminal footage above, narrated.
## Tools
### `score_conversation`
**Input:**
```json
{
"transcript": "Agent: Thanks for calling...\nCaller: Hi, I need to book...",
"call_duration": 184,
"raw_outcome": "completed",
"source_system": "Retell"
}
```
**Output:**
```json
{
"intent": "booking",
"sentiment": "positive",
"urgency": "high",
"objections": ["price"],
"outcome": "booked",
"topics": ["appointment booking", "pricing question"],
"missed_opportunity": false,
"follow_up_needed": false,
"revenue_signal": "high",
"churn_risk": false,
"ai_summary": "The caller wanted a same-day appointment and asked about price before booking. They booked a 4pm slot despite the price being a bit higher than expected."
}
```
### `get_lead_score`
Same input shape as `score_conversation`, plus an optional `repeat_caller` boolean (see [Known limitation](#known-limitation-repeat_caller) below).
**Output:**
```json
{
"score": 75,
"tier": "hot",
"signals_used": ["high urgency (+25)", "booking intent (+20)", "booked outcome (+20)", "positive sentiment (+10)"]
}
```
Scoring weights:
| Signal | Points |
|---|---|
| High urgency | +25 |
| Booking intent | +20 |
| Booked outcome | +20 |
| Repeat caller | +15 |
| Asked about pricing | +10 |
| Positive sentiment | +10 |
| Negative or frustrated sentiment | -15 |
| Dropped call | -10 |
| Low-intent inquiry | -10 |
Score is clamped to 0-100. Bands: 70-100 hot, 40-69 warm, 0-39 cold.
### `diagnose_workflow_error`
**Input:**
```json
{
"workflowJson": { "name": "...", "nodes": [...], "connections": {...} },
"errorMessage": "Cannot read properties of undefined (reading 'toUpperCase')"
}
```
**Output:**
```json
{
"rootCause": "The Code node assumes $json.body.customer always exists, but the webhook can receive a payload without a customer object.",
"fixSummary": "Add optional chaining so the node doesn't throw when customer is missing.",
"fixable": true,
"fixedWorkflow": { "...": "complete corrected workflow JSON" },
"confidence": "medium",
"notes": "Verify the caller of this node can tolerate a null email before deploying."
}
```
**Scope note:** this tool is a deliberately scoped-down port of a larger, stateful self-healing system. The original dispatches a separate evaluator sub-agent so the fix-generator never grades its own work, retries once on a rejected fix, fetches and writes back to a live n8n instance, and persists memory between runs. None of that fits inside one stateless MCP tool call, so only the diagnostic reasoning was ported: classify the error, propose the smallest possible fix, and admit honestly when a workflow edit can't fix it (expired credentials, rate limits, upstream outages, etc). Treat `fixedWorkflow` as a starting point for human or separate review, not something to auto-deploy.
### Known limitation: `repeat_caller`
The "repeat caller" signal (+15 points) can't be derived from a transcript's text alone — it requires knowing call history, which this stateless tool doesn't have access to. If you already know this from your own CRM or call log, pass `repeat_caller: true` explicitly; otherwise it defaults to `false` and that signal is simply not counted.
## Setup
```bash
npm install
cp .env.example .env
# edit .env and set ANTHROPIC_API_KEY
```
## Registering with an MCP client
Add this to your `.mcp.json` (or the equivalent config for your client), using an **absolute path** since `.mcp.json` doesn't reliably resolve relative paths across clients:
```json
{
"mcpServers": {
"tif-score-mcp": {
"command": "node",
"args": ["/absolute/path/to/tif-score-mcp/src/index.js"],
"env": {
"ANTHROPIC_API_KEY": "your-key-here"
}
}
}
}
```
Restart your MCP client afterward so it picks up the new server.
## Testing
```bash
npm run test:unit # pure scoring-math tests, no network, no API key needed
npm run test:client # spawns the real server, calls all three tools over real stdio JSON-RPC — needs ANTHROPIC_API_KEY, makes real (billed) API calls
```
You can also poke all three tools interactively without writing any code:
```bash
npx @modelcontextprotocol/inspector node src/index.js
```
## A model compatibility note
This server defaults to `claude-sonnet-4-6` with `temperature: 0` for deterministic output. That's a deliberate choice: `claude-sonnet-4-6` is the newest model that still accepts the `temperature` parameter — newer models (Opus 4.7+, Fable 5) reject it outright. If you change `TIF_MODEL` to a newer model, remove `temperature` from the calls in `src/lib/anthropic.js` first, or every tool call will fail.
## Security
Never commit real API keys. `.env` is gitignored from the first commit, and `.mcp.json` in this repo only ever contains a placeholder — fill in your real key locally, not in version control.
## Project layout
See [`CLAUDE.md`](CLAUDE.md) for a fuller orientation, [`LEARNINGS.md`](LEARNINGS.md) for decisions made while building this, and [`SKILLS.md`](SKILLS.md) for which tools/skills this project leaned on.
## License
MIT — see [`LICENSE`](LICENSE).
TDQS
Scored across 3 tools
get_lead_score and score_conversation both analyze call transcripts and clearly overlap — get_lead_score even notes it runs the same analysis as score_conversation. This creates real ambiguity about which to call. diagnose_workflow_error is completely distinct, but the two scoring tools blur boundaries significantly.
Tools use a consistent verb_noun pattern (get_lead_score, diagnose_workflow_error, score_conversation) with snake_case throughout. The naming styles are readable and consistent, though the verbs are somewhat varied (get, diagnose, score). No mixing of case conventions or chaotic patterns.
Three tools is at the thin end but not necessarily inappropriate. However, two of the three tools (get_lead_score and score_conversation) perform overlapping transcript analysis, making the actual distinct tool surface effectively two tools. The scope is unclear — the server mixes transcript scoring with completely unrelated n8n workflow diagnosis, suggesting no coherent singular purpose.
The transcript-analysis side lacks balance: it covers extraction and scoring but has no update, correction, or batch-processing capability. The n8n workflow tool appears to be an orphan — a single diagnostic tool with no companion tools for related functionality. The two unrelated domains each feel incomplete, and the overall surface has no clear lifecycle coverage.