tlly
dev.tlly/tllyVersion 1.0.0Hosted dashboards and scheduled data jobs for AI agents, with thresholds, alerts and share links.
Status
Responds429 msAnswered our handshake without sign-in.- Last checked
- Nov 14, 2026, 14:28 UTC
- Latency
- 429 ms
- Remotes responding
- 1 of 1
- Sign-in
- No sign-in
- Hosting
- Hosted outside the EU (detected)
- Registry data as of
- Nov 14, 2026, 21:12 UTC
Data from the official MCP Registry, checked by run.zero. How we check
Connect
Streamable HTTP
https://tlly.dev/mcpResponds429 msNo sign-in
Claude Code
claude mcp add --transport http tlly https://tlly.dev/mcpClaude Desktop and claude.ai
Settings > Connectors > Add custom connector
Name: tlly
Remote MCP server URL: https://tlly.dev/mcpCursor
{
"mcpServers": {
"tlly": {
"url": "https://tlly.dev/mcp"
}
}
}VS Code
code --add-mcp '{"name":"tlly","type":"http","url":"https://tlly.dev/mcp"}'Built your own server? run.zero hosts MCP servers on European infrastructure.
Tools
30 tools
Read-only | Idempotent | Open world | ||
|---|---|---|---|---|
add_annotationsAttach short dated notes to a series (e.g. 'Policy rate raised to 4.5%'); shown as markers on charts. Single-line plain text, max 280 chars; one note per date (re-adding replaces it); each counts as one data point. | Yes | No | Yes | No |
create_dashboardCreate a hosted dashboard; returns its public URL. Dashboard spec: {"title": str, "description"?: str, "range"?: "1m"|"3m"|"6m"|"ytd"|"1y"|"2y"|"5y"|"10y"|"max", "theme"?: "auto"|"light"|"dark" (default mode; auto follows the viewer's system and viewers can switch), "layout"?: "auto"|"overview"|"detail" (only the default for panels without "display": overview makes them tiles, detail makes them charts, auto makes them tiles above 8 panels), "visibility"?: "public"|"private" (default public: anyone with the link; private: only members of this account signed in to tlly.dev, so embeds and shared links stop working for others), "panels": [{"title"?: str,
"series": "name" | ["a","b"], "type"?: "line"|"area"|"bar"|"stat"|"gauge"|"bargauge"|"pie"|"heatmap"|"table", "unit"?: str, "note"?: str, "stale_after_days"?: num,
"thresholds"?: {"red_above"?, "yellow_above"?, "yellow_below"?, "red_below"?}, "range"?: (as above, overrides the dashboard's),
"section"?: str (starts a new titled section at this panel), "views"?: bool (show the Level / Y/Y % / Index buttons; default: shown unless the unit is already a rate like %, y/y or pp), "display"?: "tile"|"chart" (tile: a compact number with a sparkline, click for the full chart; chart: a card with the full chart; mix them freely, e.g. tiles for the many supporting numbers and charts for the few that need their history), "headline"?: bool (show as a big number at the top of the dashboard, under a status strip; use for the 3-6 most important), "width"?: "half"|"full", "scale"?: "linear"|"log",
"id"?: str (stable panel id, e.g. pn_abc123XYZ012; keep existing ids when editing so alerts stay attached),
"right_axis"?: [series names] (line, area and bar charts: draw these series against a second y axis on the right, for series of different sizes or units; at least one stays on the left), "right_unit"?: str (unit of the right axis; "unit" is the left's), "left_label"?/"right_label"?: str (≤40 chars, a title written vertically along the left | No | No | No | No |
create_jobSchedule a fetch job. It runs once immediately and the result is returned. Job spec: {"series": name, "cron": "m h dom mon dow" (UTC, at most every 30 min),
"request": {"url", "method"?: "GET"|"POST", "headers"?: {}, "body"?: obj|str,
"pages"?: {"param": "page", "start"?: 0, "max"?: 1-10} (json with items: bump a query parameter until an empty items array)
| {"cursor": "paging.next.after", "into": "after", "max"?: 1-10} (copy that JSON path from each response into the JSON body; stop when it is missing. A cursor that remains after max pages is an error, so a sum is never a partial total. HubSpot CRM search uses this. A json aggregate with no request.pages fails when the body still has another page, paging.next.after, has_more true, links.next, pageInfo.hasNextPage or another next cursor, instead of saving a partial total. Set pages. A response with no next marker is complete.)},
"extract": {"format": "json", "items"?: "path.to.array", "date"?: "field", "value": "field", "value_type"?: "number"|"text",
"aggregate"?: "count"|"sum"|"mean"|"min"|"max" (records per date, e.g. hourly prices to a daily mean; needs items; all but count need value), "period"?: "D"|"M"|"Q",
"where"?: [{"field": "path", "equals"|"not_equals": value}] (up to 5, with aggregate). Omit "date" with aggregate to snapshot every matching record as one point on the fetch's half hour. String numbers (HubSpot properties.amount) are summed.}
| {"format": "json_columns", "dates": "path.to.array", "values": "path.to.array", "value_type"?} (two parallel arrays, e.g. Yahoo chart: timestamp + close)
| {"format": "csv", "date"?: "COLUMN", "value": "COLUMN", "delimiter"?, "value_type"?} (omit date to stamp every row with the fetch's half hour; one value per half hour, so the last row wins. That does not sum the rows the way a JSON aggregate that omits date does. A history needs a Date column. Make a copy https://docs.google.com/spreadshee | Yes | No | No | Yes |
define_derivedCreate or replace a derived series, computed from other series every time it is read (always fresh, stores no points, counts toward the series limit). definition: {"op":"add"|"subtract"|"multiply"|"divide","a":series,"b":series} (matched by date) | {"op":"pct_change"|"diff"|"moving_average"|"rolling_min"|"rolling_max"|"shift","a":series,"n"?:1-1000} (shift: the value n points earlier) | {"op":"scale","a":series,"factor":number} | {"op":"resample","a":series,"to":"M"|"Q","how"?:"last"|"mean"|"sum"|"ffill"} (to month/quarter ends; ffill also fills gaps up to today) | {"op":"mean"|"sum","inputs":[2-20 series]} (dates all inputs have) | {"op":"status","a":series,"red"?:[rule],"yellow"?:[rule]} → text labels red/yellow/green (red if any red rule holds, else yellow if any yellow rule, else green); rule: {"series"?:name (default a; read as of each date of a),"above"|"below":number,"for"?:1-24 (holds for the last k points)} or {"all":[2-10 rules]}; at most 10 rules | {"op":"composite","inputs":[status series],"min_inputs"?:n} → 0-100 score (red 2, yellow 1, green 0) | {"op":"count","inputs":[status series],"value":"red"|"yellow"|"green","share"?:true (percent)}. Inputs may themselves be derived (up to 10 levels; one read may touch at most 200 stored series). Example: a yield-curve spread {"op":"subtract","a":"govt_10y","b":"tbill_3m"}. | Yes | No | Yes | No |
delete_annotationsDelete annotations of a series on the given dates, or all of them if dates is omitted. | Yes | No | Yes | No |
delete_dashboardDelete a dashboard (series data is kept). | Yes | No | Yes | No |
delete_jobDelete a job (its series data is kept). | Yes | No | Yes | No |
delete_seriesDelete all points of a series, or remove a derived series' definition. | Yes | No | Yes | No |
get_integration_skillRead one integration playbook by id: what to track, the minimum credential and scopes, the exact tlly job, and gotchas. google-sheets and json-api also return a template field (Make a copy, public CSV, downloads). Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/copy. Public CSV https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/export?format=csv&gid=1746689811. Downloads /templates/google-sheets.csv and /templates/google-sheets.xlsx. Public: no API key required. Call this before inventing an API URL or scope. | No | Yes | Not declared | No |
get_seriesRead the latest points of a series. | No | Yes | Not declared | No |
get_statusHealth check: failing jobs, stale or empty panels, dashboards, storage usage. Call this to verify everything works. | No | Yes | Not declared | No |
get_usageStorage usage against quotas (data points = values + annotations, series, dashboards, jobs) and the largest series. Check before large writes. | No | Yes | Not declared | No |
import_onceFetch a job spec once and save the result, without scheduling it or using a job slot ("cron" is optional and ignored). Use for one-off dashboards whose data doesn't need to update; points count toward the data quota. Job spec: {"series": name, "cron": "m h dom mon dow" (UTC, at most every 30 min),
"request": {"url", "method"?: "GET"|"POST", "headers"?: {}, "body"?: obj|str,
"pages"?: {"param": "page", "start"?: 0, "max"?: 1-10} (json with items: bump a query parameter until an empty items array)
| {"cursor": "paging.next.after", "into": "after", "max"?: 1-10} (copy that JSON path from each response into the JSON body; stop when it is missing. A cursor that remains after max pages is an error, so a sum is never a partial total. HubSpot CRM search uses this. A json aggregate with no request.pages fails when the body still has another page, paging.next.after, has_more true, links.next, pageInfo.hasNextPage or another next cursor, instead of saving a partial total. Set pages. A response with no next marker is complete.)},
"extract": {"format": "json", "items"?: "path.to.array", "date"?: "field", "value": "field", "value_type"?: "number"|"text",
"aggregate"?: "count"|"sum"|"mean"|"min"|"max" (records per date, e.g. hourly prices to a daily mean; needs items; all but count need value), "period"?: "D"|"M"|"Q",
"where"?: [{"field": "path", "equals"|"not_equals": value}] (up to 5, with aggregate). Omit "date" with aggregate to snapshot every matching record as one point on the fetch's half hour. String numbers (HubSpot properties.amount) are summed.}
| {"format": "json_columns", "dates": "path.to.array", "values": "path.to.array", "value_type"?} (two parallel arrays, e.g. Yahoo chart: timestamp + close)
| {"format": "csv", "date"?: "COLUMN", "value": "COLUMN", "delimiter"?, "value_type"?} (omit date to stamp every row with the fetch's half hour; one value per half hour, so the last row wins. That does | Yes | No | No | Yes |
list_annotationsRead the latest annotations of a series. | No | Yes | Not declared | No |
list_dashboard_versionsList a dashboard's saved versions (newest first) with which one is live and a members-only preview URL for each. | No | Yes | Not declared | No |
list_dashboardsList dashboards with their URLs. | No | Yes | Not declared | No |
list_integration_skillsList integration setup playbooks (Stripe, Shopify, Sheets, and the other sources tlly can read). A spreadsheet starts from the KPI template. Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/copy. Public CSV https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/export?format=csv&gid=1746689811. Downloads /templates/google-sheets.csv and /templates/google-sheets.xlsx. Public: no API key required. | No | Yes | Not declared | No |
list_jobsList scheduled fetch jobs with their last run status and any error message. | No | Yes | Not declared | No |
list_secretsList the names of the workspace's stored secrets (API keys for private sources) and the host each may be sent to. Use one in a job's url, headers or body as {{secret:NAME}}. | No | Yes | Not declared | No |
list_seriesList all series with their type (number or text), point counts and date ranges; text series also list their values. | No | Yes | Not declared | No |
publish_dashboard_versionMake a saved version of a dashboard the live one (also to roll back). The dashboard keeps its URL and visibility. | No | No | Yes | No |
push_pointsWrite dated values to a series (upsert by date). A date is YYYY-MM-DD (midnight UTC), or a time such as 2026-09-30T14:30Z, floored to the half hour. Use when you compute data yourself. Values are numbers, or short text labels for a text series (e.g. a rating or stance; see Data policy). A series' first write fixes its type. | Yes | No | Yes | No |
rotate_dashboard_urlGive a dashboard a new public URL and disable the old one at once (e.g. if a link leaked). Spec and data are unchanged. | Yes | No | No | No |
run_job_nowRun a scheduled fetch job immediately, store any new points, and return the run result. | Yes | No | No | Yes |
set_alert_webhookSet or clear the workspace webhook URL for job-failure and dashboard status alerts (pass an empty url to clear). When events fire, tlly POSTs JSON to that URL (Slack and Discord incoming webhooks get a short text payload instead). Events include job_failed, status_changed (keep panel "id" fields in specs so alerts stay attached), and threshold_crossed. Email alerts work without a webhook. Caps: 12 emails and 60 webhooks per account per hour; the same panel is not alerted twice within an hour. | Yes | No | Yes | Yes |
set_panel_alertSet when one dashboard panel sends alert email and/or a webhook (colour change, threshold cross, or off). trigger: change (default), red, notgreen, above, below, or off; above/below need limit. notify: both, email, or webhook. panel is the 0-based index in the live spec. Saving can send immediately if a limit was just crossed (unless this panel alerted within the last hour). Later sends happen after scheduled fetches. Default change+both removes a custom rule. Does not change colour lines or save a dashboard version. | Yes | No | No | Yes |
set_stale_alertConfigure when tlly sends stale-data alert email and webhooks because a series stopped updating. after: auto (from the job schedule and point spacing, at least two days), off, hours, or a window like 48h or 7d. Use dashboard_id for every series on a dashboard, series for one series, or both for one series on one dashboard. One email and one webhook when it goes quiet; another when a newer point arrives. Counts toward the hourly caps. | Yes | No | Yes | Yes |
test_jobDry-run a job spec: fetch, extract and transform, return a preview. Saves nothing. Job spec: {"series": name, "cron": "m h dom mon dow" (UTC, at most every 30 min),
"request": {"url", "method"?: "GET"|"POST", "headers"?: {}, "body"?: obj|str,
"pages"?: {"param": "page", "start"?: 0, "max"?: 1-10} (json with items: bump a query parameter until an empty items array)
| {"cursor": "paging.next.after", "into": "after", "max"?: 1-10} (copy that JSON path from each response into the JSON body; stop when it is missing. A cursor that remains after max pages is an error, so a sum is never a partial total. HubSpot CRM search uses this. A json aggregate with no request.pages fails when the body still has another page, paging.next.after, has_more true, links.next, pageInfo.hasNextPage or another next cursor, instead of saving a partial total. Set pages. A response with no next marker is complete.)},
"extract": {"format": "json", "items"?: "path.to.array", "date"?: "field", "value": "field", "value_type"?: "number"|"text",
"aggregate"?: "count"|"sum"|"mean"|"min"|"max" (records per date, e.g. hourly prices to a daily mean; needs items; all but count need value), "period"?: "D"|"M"|"Q",
"where"?: [{"field": "path", "equals"|"not_equals": value}] (up to 5, with aggregate). Omit "date" with aggregate to snapshot every matching record as one point on the fetch's half hour. String numbers (HubSpot properties.amount) are summed.}
| {"format": "json_columns", "dates": "path.to.array", "values": "path.to.array", "value_type"?} (two parallel arrays, e.g. Yahoo chart: timestamp + close)
| {"format": "csv", "date"?: "COLUMN", "value": "COLUMN", "delimiter"?, "value_type"?} (omit date to stamp every row with the fetch's half hour; one value per half hour, so the last row wins. That does not sum the rows the way a JSON aggregate that omits date does. A history needs a Date column. Make a copy https://docs.google.com/sp | No | Yes | Not declared | Yes |
update_dashboardReplace a dashboard's spec (URL stays the same). Every update is saved as a numbered version. With "draft": true it is saved without going live: viewers keep seeing the live version, members can preview the draft at the returned preview_url, and publish_dashboard_version makes it live. Use drafts for larger changes and publish when the user confirms. Dashboard spec: {"title": str, "description"?: str, "range"?: "1m"|"3m"|"6m"|"ytd"|"1y"|"2y"|"5y"|"10y"|"max", "theme"?: "auto"|"light"|"dark" (default mode; auto follows the viewer's system and viewers can switch), "layout"?: "auto"|"overview"|"detail" (only the default for panels without "display": overview makes them tiles, detail makes them charts, auto makes them tiles above 8 panels), "visibility"?: "public"|"private" (default public: anyone with the link; private: only members of this account signed in to tlly.dev, so embeds and shared links stop working for others), "panels": [{"title"?: str,
"series": "name" | ["a","b"], "type"?: "line"|"area"|"bar"|"stat"|"gauge"|"bargauge"|"pie"|"heatmap"|"table", "unit"?: str, "note"?: str, "stale_after_days"?: num,
"thresholds"?: {"red_above"?, "yellow_above"?, "yellow_below"?, "red_below"?}, "range"?: (as above, overrides the dashboard's),
"section"?: str (starts a new titled section at this panel), "views"?: bool (show the Level / Y/Y % / Index buttons; default: shown unless the unit is already a rate like %, y/y or pp), "display"?: "tile"|"chart" (tile: a compact number with a sparkline, click for the full chart; chart: a card with the full chart; mix them freely, e.g. tiles for the many supporting numbers and charts for the few that need their history), "headline"?: bool (show as a big number at the top of the dashboard, under a status strip; use for the 3-6 most important), "width"?: "half"|"full", "scale"?: "linear"|"log",
"id"?: str (stable panel id, e.g. pn_abc123XYZ012; keep existing ids when editing so alerts stay attached),
"right_axis"?: [series names] (line, ar | No | No | No | No |
update_jobReplace a job's spec, or pause it with enabled=false. Job spec: {"series": name, "cron": "m h dom mon dow" (UTC, at most every 30 min),
"request": {"url", "method"?: "GET"|"POST", "headers"?: {}, "body"?: obj|str,
"pages"?: {"param": "page", "start"?: 0, "max"?: 1-10} (json with items: bump a query parameter until an empty items array)
| {"cursor": "paging.next.after", "into": "after", "max"?: 1-10} (copy that JSON path from each response into the JSON body; stop when it is missing. A cursor that remains after max pages is an error, so a sum is never a partial total. HubSpot CRM search uses this. A json aggregate with no request.pages fails when the body still has another page, paging.next.after, has_more true, links.next, pageInfo.hasNextPage or another next cursor, instead of saving a partial total. Set pages. A response with no next marker is complete.)},
"extract": {"format": "json", "items"?: "path.to.array", "date"?: "field", "value": "field", "value_type"?: "number"|"text",
"aggregate"?: "count"|"sum"|"mean"|"min"|"max" (records per date, e.g. hourly prices to a daily mean; needs items; all but count need value), "period"?: "D"|"M"|"Q",
"where"?: [{"field": "path", "equals"|"not_equals": value}] (up to 5, with aggregate). Omit "date" with aggregate to snapshot every matching record as one point on the fetch's half hour. String numbers (HubSpot properties.amount) are summed.}
| {"format": "json_columns", "dates": "path.to.array", "values": "path.to.array", "value_type"?} (two parallel arrays, e.g. Yahoo chart: timestamp + close)
| {"format": "csv", "date"?: "COLUMN", "value": "COLUMN", "delimiter"?, "value_type"?} (omit date to stamp every row with the fetch's half hour; one value per half hour, so the last row wins. That does not sum the rows the way a JSON aggregate that omits date does. A history needs a Date column. Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNG | Yes | No | No | Yes |
Details
- Registry status
- Active
- Published
- Oct 30, 2026
- Updated
- Oct 30, 2026
- Categories (suggested)
- Developer toolsData and analyticsSuggested categories are assigned automatically from the registry text and may be wrong. Verified operators can correct them.
- GDPR
- Not declared